Only this pageAll pages
Powered by GitBook
Couldn't generate the PDF for 267 pages, generation stopped at 100.
Extend with 50 more pages.
1 of 100

🏆 เนื้อหาหลัก

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

มือใหม่หัดเขียนโค้ด

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Puzzle

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

พื้นฐานที่ควรต้องรู้

Loading...

2020

2020-082020-032020-022020-01

สลัดผัก

มาทำความเข้าใจกันหน่อยนุงน๊า

💌 ความรู้สึกในใจ

ผมรู้สึกว่าการเขียนโค้ดเป็นเรื่องสนุก เพราะมันเหมือนได้เล่นเกมแก้ปัญหาตลอดเวลา และถ้าเราได้ทำสิ่งที่เรารักทุกวัน ชีวิตนี้ก็เหมือนเราได้เล่นเกมตลอดไปโดยไม่ต้องทำงานเลย 💖 (ดช.แมวน้ำ ได้กล่าวไว้)

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

ทุกอย่างในเว็บนี้ฟรีทั้งหมดและจะฟรีตลอดไปแน่นอนฮ๊าฟฟฟฟฟ 😘

🚀 การใช้งานเว็บนี้

🛰️ อัพเดท

เมื่อมีอัพเดทใหม่ๆเข้ามา ดช.แมวน้ำ จะเอาไปใส่ไว้ในหน้าด้านล่างนี้นะครับ จะได้ไล่ดูง่ายๆ

🛰️ เนื้อหา

เว็บนี้จะแบ่งเนื้อหาออกเป็นหมวดๆเยอะเต็มไปหมด ดูได้จากเมนูด้านซ้าย และเนื้อหาแต่ละเรื่องจะมีสัญลักษณ์อยู่ด้านหน้า เพื่อบอกว่าแมวน้ำที่มาอ่านจะต้องมีความรู้ระดับไหนไว้ด้านหน้านะจ๊ะ ส่วนเจ้าสัญลักษณ์ต่างๆก็ตามตารางด้านเลยเบย

สำหรับแมวน้ำท่านไหนที่สนใจอยากช่วยสร้างเนื้อหา หรืออยากให้สอนเรื่องอะไรรบกวนไป comment ใต้วีดีโอตัวนี้เอานะขอรับ >>

2020-11

✏️ ประวัติการอัพเดท

สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก Facebook Mr.Saladpuk ได้นะครับ 😍

17/11/2020

  • เพิ่มบทความ - โจทย์ที่ไอสไตน์บอกว่ามีคนแค่ 2% บนโลกที่ตอบได้

16/11/2020

  • เพิ่มบทความ - โจทย์สอบสัมภาษณ์ผู้บริหาร CP

13/11/2020

  • เพิ่มบทความ - โจทย์สอบสัมภาษณ์เข้า Saladpuk

12/11/2020

  • เพิ่มบทความ - โจทย์สอบสัมภาษณ์เข้า Amazon

10/11/2020

  • เพิ่มบทความ - โจทย์สอบสัมภาษณ์เข้า Saladpuk

  • เพิ่มบทความ - โจทย์สอบสัมภาษณ์เข้า Google

08/11/2020

  • เพิ่มบทความ - แนวคิดในการควบคุม object ให้ทำงานดั่งใจ 😈

07/11/2020

  • เพิ่มบทความ - 🤔 อยากใช้ Windows กับ Linux พร้อมกันทำไง

04/11/2020

  • เพิ่มบทความ - ออกแบบไงให้ของทำงานร่วมกันได้ง่ายๆ

02/11/2020

  • เพิ่มบทความ - ส่ง 🖼️ Container Images ให้คนอื่นได้ใช้กัน

สัญลักษณ์

ความหมาย

👶

สำหรับแมวน้ำที่พึ่งหัดเริ่มเขียนโปรแกรมใหม่ๆ

👦

สำหรับแมวน้ำที่เขียนมาได้ซักพักใหญ่ๆแล้ว

🤴

สำหรับแมวน้ำที่มีความรู้แน่นพุง

⏳

คอร์สนั้นกำลังเขียนอยู่

📰มีอะไรใหม่บ้าง
Saladpuk
🧓 Einstein's Riddle 01
🎩 CP หมวก 5 ใบ
💊 ยาต้านโควิด
🌉 Amazon Interview 01
🥇 ทองเก๊
🐴 Google Interview 01
📪 Proxy Pattern
🔄 WSL
🔌 Adapter Pattern
📢 Docker Push

พื้นฐาน

ระดับมัธยม

ความรู้พื้นฐานที่ในระดับมือใหม่ควรจะต้องรู้ เชื่อไหมว่าถ้าเข้าใจเนื้อหา พื้นฐาน ทั้งหมดนี่ได้จริงๆก็สามารถเขียนโปรแกรมส่วนใหญ่ 70% ได้แล้ว 😍

1.โปรแกรมที่ต้องลง2.โครงสร้างของโค้ด3.ชนิดของข้อมูล4.การสร้างตัวแปร5.คำสั่งพื้นฐาน6.การแปลงข้อมูล7.การเปรียบเทียบค่า8.การตัดสินใจด้วย IF statements9.การตัดสินใจด้วย Switch statements10.การทำงานซ้ำๆด้วย While11.การทำงานซ้ำๆด้วย Do While12.การทำงานซ้ำๆด้วย For13.การแก้โจทย์จากรูป14.มารู้จักกับ Array กัน

2.โครงสร้างของโค้ด

💬 หลังจากที่ติดตั้งโปรแกรมไปละ คราวนี้ลองมาดูว่าเราจะเริ่มเขียนโค้ดตรงจุดไหน และโค้ดมันมีอะไรบ้างนะ

🎯 สรุปสั้นๆ

👨‍🚀 จุดแรกที่เขียนโค้ด

using System;

namespace myApp
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Hello World!");
        }
    }
}

เวลาที่เราจะเขียนโค้ดเราจะเขียนในวงเล็บ { } ของ Main() นะกั๊ฟ ถ้าดูจากโค้ดด้านบนคือบรรทัดที่ 9 ไงล่าาา

2020-10

✏️ ประวัติการอัพเดท

สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก Facebook Mr.Saladpuk ได้นะครับ 😍

28/10/2020

  • เพิ่มบทความ - ลองสร้าง Image ตัวแรกของเรากัน 😉

27/10/2020

  • เพิ่มบทความ - แหล่งเก็บ Docker Images ที่ใหญ่ที่สุดในโลก 😍

26/10/2020

  • เพิ่มบทความ - อยากใช้เจ้าวาฬน้ำเงินแต่ขี้เกียจจำคำสั่งทำไงดี 🤔

25/10/2020

  • เพิ่มบทความ - ลองใช้งานเจ้าวาฬน้ำเงินครั้งแรกกัน 🐳

24/10/2020

  • เพิ่มบทความ - สิ่งไม่มีชีวิตที่เรียกว่าคอนเทนเนอร์

23/10/2020

  • เพิ่มบทความ - คอร์ส Docker หลักแสนที่ไม่ต้องจ่ายเงินเรียน 😘

2020-01

✏️ ประวัติการอัพเดท

สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

23.Polymorphism

💬 หลังจากที่เห็นน้ำจิ้มตัวแรกในโลกของ object กันไปละ คราวนี้เราจะมาดูความสามารถที่ได้จากการทำ Inheritance ที่เรียกว่า Polymorphism กันดูบ้างว่ามันมาช่วยแก้ปัญหาเรื่องอะไรได้อีกบ้างกันครับ

🎥 ตัวอย่างการใช้ Polymorphism

14.มารู้จักกับ Array กัน

💬 พอได้ลองเขียนโค้ดไปได้ซักพัก พวกเราจะพบว่าตัวแปรที่เราสร้างไว้มันค่อนข้างวุ่นวายเลยใช่ไหม ลองคิดดูนะว่าถ้าเราต้องเก็บคะแนนของนักเรียน 1 คนมีต้องมีตัวแปร 1 ตัวชิมิ แต่ถ้าเกิดว่าเราต้องเก็บข้อมูลของนักเรียนซัก 100 คนขึ้นมาละ? ตัวแปรของเราคงมีอย่างต่ำคือ 100 เลยตัวใช่ไหม? ดังนั้นในรอบนี้เราจะลองมารู้จักกับการจัดการกับตัวแปรประเภทเดียวที่ใช้ซ้ำๆกันดู นั่นคือเรื่องของ Array นั่นเอง

🎥 ตัวอย่างการใช้ Array กับ For loop

13.การแก้โจทย์จากรูป

😢 ปัญหา

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

4.การสร้างตัวแปร

อะเคร๊ หลังจากรู้แล้วว่าการเขียนโค้ดนั้นมันจะมีข้อมูลที่เราต้องทำงานด้วย 4 ชนิดคือ int, double, string, bool กันละ ในรอบนี้เราจะลองสั่งให้คอมพิวเตอร์ช่วยจำข้อมูลแต่ละประเภทดูหน่อยละกันนะ

เวลาที่เราให้คอมมันช่วยจำอะไรสักอย่าง เราจะเรียกสิ่งที่มันจำนี้ว่า ตัวแปร หรือ variable นั่นเอง

เช่น int money = 3; หมายถึงเราให้คอมจำว่าตัวแปรที่ชื่อว่า money มันมีค่าเป็น 3 และมันจำได้เฉพาะตัวเลขจำนวนเต็มเท่านั้น

22.การสืบทอด Inheritance

💬 หลังจากที่เราเริ่มใช้งานคลาสมาได้ในระดับนึงละ ซึ่งผมอยากจะบอกว่าที่ผ่านมาทั้งหมดเป็นแค่เพียงน้ำจิ้มของ C# เท่านั้นเอง!! ดังนั้นในรอบนี้เราจะเริ่มดื่มด่ำกับโลกของ C# ที่แท้จริงเลยละกัน ซึ่งนั่นคือโลกของ object หรือสิ่งที่เราเรียกว่าการเขียนโปรแกรมแบบ Object-Oriented Programming หรือ OOP นั่นเอง

🎥 ตัวอย่างการทำ Inheritance

Tips

เกร็ดความรู้

ตรงนี้จะเป็นการรวบรวมเกร็ดความรู้ในการเขียนภาษา C# ไว้ครับ ซึ่งเราจะไม่รู้เลยซักตัวก็ได้ เพียงแต่ถ้าเราต้องการเป็น professional และสามารถเขียนโค้ดให้เป็น Production Code แล้วล่ะก็ผมแนะนำให้อ่านครับ

เนื้อหาตรงนี้จะมีบ้างอะไรบ้างผมก็ไม่รู้เหมือนกัน จำได้ว่าควรจะต้องบอกเรื่องอะไรก็จะเอามากองๆไว้ตรงนี้ครับ

  • คิดออกเดี๋ยวเอามาเติมตรงนี้เรื่อยๆละกัน

2019

Updates

ระดับกลาง

ระดับมหาลัย

ความรู้ในระดับกลาง ซึ่งโดยปรกติคนที่จบมหาลัยหลักสูตร Computer Science ได้ควรจะต้องเข้าใจเรื่องเหล่านี้หมดแล้ว 😍

🖼️ Container Image
🗃️ Docker Registry
🛠️ Docker Tools
🃏 Docker Exercise 01
📦 Docker Containers
🐳 Docker
28/01/2020
  • อัพเดทซีรี่ Saladpuk Games เรื่อง ติดตั้ง Unity กัน

  • อัพเดทซีรี่ Saladpuk Games เรื่อง Unity คือไย?

27/01/2020

  • ปีใหม่มาลองหัดเขียนเกมกันด้วย Unity กันดีกั่ว Saladpuk Games

Facebook Blog: Mr.Saladpuk
💡C# version 8.0
💡Boxing & Unboxing
2019-11
2019-10
2019-09
2019-08
15.Value type vs Reference type
16.ลดงานซ้ำๆด้วย Method
17.มารู้จักกับ Class & Field กัน
18.มารู้จักกับ Constructor กันบ้าง
19.มาเขียน Method ใน Class กัน
20.มารู้จักกับ Property กัน
21.ลองใช้คลาสแบบจริงจังบ้าง
22.การสืบทอด Inheritance
23.Polymorphism
24.Abstract Class
25.Interface
26.Namespace
27.Enum
28.Exception handler
29.ลงลึกกับ string
30.StringBuilder เพื่อนคู่ string
🎯 สรุปสั้นๆ

👨‍🚀 สิ่งที่สามารถทำได้เมื่อทำ inheritance แล้ว

1.การเปลี่ยนรูปของคลาส Base Class สามารถเก็บ object ของ Derived Class ของมันได้ ซึ่งความสามารถนี้คือหัวใจหลักของ Polymorphism เลยล่ะ

เจ้าลูกทรพี! ในทางกลับกัน Derived Class จะไม่สามารถเก็บ object ของ Base Class ได้

2.virtual & override keyword เราสามารถใช้คำสั่ง virtual ให้กับ method ของ Base Class ได้ เพื่อบอกว่าถ้า Derived Class ตัวไหนอยากเปลี่ยนการทำงานไปจาก Base Class ก็สามารถเปลี่ยนได้ โดย

new keyword ถ้า Derived Class อยากตัดความสัมพันธ์จาก Base Class แล้ว ก็สามารถทำได้ด้วยการใช้ new keyword ไปวางไว้หน้า property หรือ method ที่เป็น virtual นั่นเอง

3.base keyword เป็นการบอกว่าให้เรียกใช้งานความสามารถนั้นๆจาก Base Class

4.sealed keyword เป็นการระบุว่า ณ จุดนั้นๆ ไม่อนุญาติให้คลาสอื่นๆมาสืบทอดหรือเปลี่ยนแปลงมันได้อีกต่อไปแล้ว (เป็นหมันนั่นเอง)

sealed class คือคลาสที่ไม่ยอมให้คลาสอื่นสืบทอดได้อีก

sealed member คือ member ที่ไม่ยอมให้ Derived Class มาทำการแก้ไขมันได้อีกแล้ว เช่น sealed method นั่นเอง

🎯 สรุปสั้นๆ

👨‍🚀 Array คือ

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

👨‍🚀 ประเภทของ Array

Array ที่เราใช้กันอยู่มีทั้งหมด 3 แบบ โดยแต่ละแบบก็จะเหมาะกับประเภทของคนละชนิดกันด้วยนะจุ๊

  1. Single Dimensional Array

  2. Multidimensional Array

  3. Jagged Array

😄 วิธีแก้ปัญหา

ดช.แมวน้ำ ขอเสนอวิธีการแก้โจทย์โดยการวาดรูปดูนะครับ น่าจะไม่มีที่ไหนสอนแบบนี้ในโลกเลยนะผมมโนขึ้นมาเองล้วนๆ + พบว่ามันช่วยให้หลายๆคนเข้าใจได้ง่ายขึ้นเยอะเลย

ปรกติวิธีการคิดเพื่อแก้โจทย์ เขาจะสอนให้เราเขียนสิ่งที่เรียกว่า flow chart หรือที่เรียกว่าแผนภาพการทำงานของโปรแกรมนั่นแหละ ซึ่งผมมองว่า (ขี้เกียจสอนเฟร้ย) มันวุ่นวายเกินไป เพราะเราต้องไปทำความเข้าใจเรื่อง flow chart ก่อนถึงจะมาทำต่อได้ แต่ทุกคนวาดภาพได้แน่นอนอยู่ล่ะ โดยไม่สนใจนะว่าสวยหรือเปล่า ดังนั้นผมเลยเลือกให้ทุกท่านลองหยิบดินสอกระดาษมาลองทำตามกันดูครับ

🎯 สรุปสั้นๆ

👨‍🚀 ขั้นตอนในการแก้โจทย์จากรูป

  1. เขียนออกมาก่อนว่าโปรแกรมต้องทำอะไรบ้าง

  2. ลองวาดรูปการทำงานในแต่ละขั้นตอนออกมา

  3. ลองเขียนดูว่าแต่ละขั้นตอนเราต้องใช้ความรู้เรื่องอะไรบ้าง เช่น ชนิดตัวแปร, loop แบบไหน

  4. เลือกว่าในแต่ละขั้นตอนควรจะคำสั่งแบบไหน

  5. เอาของที่ได้จากขั้นตอนที่ 4 ไปลองเขียนโค้ดทีละขั้นตอนดู

🎯 สรุปสั้นๆ

👨‍🚀 การสร้างตัวแปรให้คอมช่วยจำ

ชนิดตัวแปร ชื่อตัวแปร;

👨‍🚀 กฎในการสร้างตัวแปร

  • ชื่อตัวแปรห้ามซ้ำกัน แม้จะเป็นคนละชนิดกันก็ตาม

  • ชื่อตัวแปรตัวเล็กตัวใหญ่มีความหมาย (case sensitive) ดังนั้น a กับ A ถือว่าเป็นตัวแปรคนละตัวกัน

  • ห้ามตั้งชื่อแล้วเว้นวรรค (ถ้าจะทำให้ใช้ตัว _ แทน)

  • ห้ามตั้งชื่อขึ้นต้นด้วยตัวเลข หรือ ตัวอักษรพิเศษ (ยกเว้นตัว @ และ _ สามารถใช้เป็นตัวต้นได้)

  • ห้ามตั้งชื่อซ้ำกับคำสงวนของภาษา C#

สามารถตั้งชื่อภาษาไทยหรือเลขไทยได้นะ แต่แนะนำว่าอย่าทำเพราะมันจะทำให้มีปัญหาเวลาใช้งานจริง

ตัวอักษรพิเศษ เช่น !@#$%^&*()_+\|/-+

คำสงวน คือคำที่เราห้ามใช้ เพราะภาษานั้นๆจะขอเอาไว้ใช้เอง เช่นคำว่า int, string, double, class

🎯 สรุปสั้นๆ

👨‍🚀 Inheritance

คือการสืบทอดความสามารถจากคลาสนึงไปยังอีกคลาสนึง โดยคลาสที่เป็นต้นแบบเราเรียกมันว่า Base Class หรือคลาสแม่ในภาษาไทย ส่วนคลาสที่สืบทอดความสามารถมาเราเรียกมันว่า Derived Class (บางตำราเรียกมันว่า Sub Class) ส่วนในภาษาไทยเราเรียกมันว่าคลาสลูก

คลาส 1 คลาส สามารถมี Base Class ได้เพียงตัวเดียวเท่านั้นนะ

👨‍🚀 ความสัมพันธ์

Derived Class จะมีทุกอย่างที่ Base Class มี เช่น Fields, Methods บลาๆ แต่ยกเว้นสิ่งที่เป็น private แต่ในทางตรงกันข้ามกัน Base Class จะไม่รับรู้อะไรที่เกิดจาก Derived Class ของมันเลยแม้แต่นิดเดียว

27.Enum

💬 เคยมีปัญหาเวลาที่จะกำหนดค่าของที่มันเป็นข้อมูลเซ็ตเดิมๆกันไหม เช่นกำหนดว่าวันนี้เป็นวันอะไร จันทร์ อังคาร พุธ... ซึ่งอีกหน้านึงก็ต้องกำหนดค่าวัน จันทร์ อังคาร พุธ ... อีกไรงี้ ในรอบนี้เราจะมาดูการสร้างสิ่งที่เรียกว่า Enum กันดูบ้างนะว่ามันจะมาช่วยเราเรื่องนี้ยังไง

🎯 สรุปสั้นๆ

👨‍🚀 Enum คือ

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

ตัวอย่างการสร้างเซ็ตข้อมูลของวันในสัปดาห์

ตัวอย่างการแปลง enum เป็นตัวเลข

ตัวอย่างการแปลงตัวเลขกลับไปเป็น enum

ถ้าเราใส่ตัวเลขที่ไม่มีอยู่ในเซ็ตของ enum เราจะได้เลขนั้นๆออกมาแทน

👨‍🚀 การแปลง string เป็น Enum

เราสามารถแปลง string ไปเป็น enum ได้ด้วยการใช้ Enum.Parse() หรือ Enum.TryParse() ครับตามตัวอย่างด้านบน

Enum.Parse() ถ้า string ตัวนั้นไม่ตรงกับข้อมูลในเซ็ตนั้นเลย มันจะเกิด error ออกมา ดังนั้นผมแนะนำว่าควรจะใช้ Enum.TryParse() มากกว่าครับ

25.Interface

💬 เคยหงุดหงิดไหมว่าเวลาที่เราเรียกใช้ method ประเภทเดิมๆ แต่ชื่อมันไม่ซ้ำกันเลย เช่น จะเปิดทีวีต้องเรียกใช้ method TurnOn พอจะเปิดพัดลมต้องเรียกใช้ method method SwitchOn พอจะเปิดไฟต้องเรียกใช้ method LightOn ไรงี้ ทั้งๆที่ทั้งหมดมันก็เป็นการสั่งให้เปิดเหมือนกันแท้ๆ แต่ทำไมแต่ละคลาสต้องตั้งชื่อต่างกันด้วยยยย แค่คิดก็หงุดหงิดแล้วที่ต้องไปคอยจำชื่อ method แต่ละตัว ดังนั้นในรอบนี้เราจะมาทำการสร้างมาตรฐานการเขียนโค้ดด้วยสิ่งที่ชื่อว่า Interface กันนะฮ๊าฟฟฟฟ (อารมณ์ดีละพอได้ยินชื่อนี้)

🎯 สรุปสั้นๆ

👨‍🚀 Interface คือ

แบบอย่างหรือมาตรฐานของคลาส โดยมันจะบังคับว่าคลาสที่ implement interface จะต้องมีทุกอย่างที่ interface นั้นๆมีด้วย

Microsoft standard โดยปรกติเวลาที่ทาง Microsoft สร้าง interface ขึ้นมา เขาจะใส่ตัวไอใหญ่ I นำหน้าไว้เสมอ เช่น ICloneable, IComparable, IDisposable บลาๆ ซึ่งเราจะไม่ทำตามก็ทำงานได้นะ และภาษาอื่นๆก็ไม่ได้ทำแบบนี้ด้วย แต่ถ้าเราเขียนภาษา C# เป็นชีวิตจิตใจอยู่แล้วผมนะนำว่าเราควรจะทำตามมาตรฐานนี้ไว้ครับเพราะมันเป็นมาตรฐานสากลของ developer สาย .NET นั่นเอง

Interface 1.ไม่ได้เอาไว้สร้าง object แต่สามารถเก็บ object ของ class ที่ implement interface นั้นๆได้ 2.ของที่อยู่ใน interface จะไม่สามารถมี body หรือ implementation ได้ (ยกเว้น C# version 8.0 ขึ้นไป)

C# version 8.0 ใน C# version 8.0 หรือใน .NET Core 3 นั้นตัว interface จะสามารถมีสิ่งที่เรียกว่า Default interface members หรือพูดง่ายๆคือของใน interface สามารถมี body ได้แล้วนั่นเอง

ตัวอย่างการทำ Default interface members

C# 8.0 มีอะไรใหม่บ้าง 1.อ่านเอาได้จาก 2.การใช้ default interface members

2020-08

✏️ ประวัติการอัพเดท

สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก Facebook Blog: Mr.Saladpuk ได้นะครับ 😍

ช่วงนี้งานของ ดช.แมวน้ำ เยอะม๊วก เลยไม่ค่อยได้อัพเดทบทความเท่าไหร่นะจ๊ะ แต่จะค่อยๆทยอยลงเรื่อยๆเมื่อมีโอกาศ ส่วนใครที่ด่วนอยากได้ความรู้ตรงไหนก็ทัก Facebook ส่วนตัวมาได้นะกั๊ฟ

23/08/2020

  • เพิ่มบทความใหม่ - เขียนเทสมันจะช่วยให้การออกแบบมันดีขึ้นได้ไง ?

20/08/2020

  • เพิ่มบทความใหม่ - แนวคิดของโปรแกรมเมอร์มืออาชีพมีไรบ้าง

14/08/2020

  • เพิ่มบทความใหม่ - การตั้งชื่อของลุงบ๊อบ

13/08/2020

  • เพิ่มบทความใหม่ - การคอมเมนต์โค้ดของลุงบ๊อบ

12/08/2020

  • เพิ่มบทความใหม่ - การทำ Clean Code ในมุมของมหาเทพเป็นยังไงบ้าง

08/08/2020

  • เพิ่มบทความ - คู่มือในการออกแบบการคุยกันระหว่างเซอร์วิส

07/08/2020

  • เพิ่มบทความ - Time complexity

05/08/2020

  • เพิ่มบทความ - Algorithms Complexity (Big-O) คือไยหว่า ?

04/08/2020

  • เพิ่มบทความ - เรียนไปทำไมเสียเวลาชีวิตจุง

30.StringBuilder เพื่อนคู่ string

💬 ในรอบนี้เราจะมาดูชัดๆในเรื่องของความเป็น Immutable ของ string ว่าถ้าเราต่อ string ไปเรื่อยๆมันจะเกิดปัญหาอะไรขึ้นกับโปรแกรมเรา และเราจะแก้ไขปัญหานั้นยังไง ซึ่งก็ไม่เห็นต้องบอกเลยหัวเรื่องก็จั่วอยู่แล้วว่าเป็น StringBuilder ไงล่ะ

🎯 สรุปสั้นๆ

👨‍🚀 การต่อ string ที่ไม่ซับซ้อน

อย่าต่อโดยใช้เครื่องหมาย + เน้นย้ำคำโตๆว่า อย่างทำ!! โปรแกรมไม่ได้พังหรอกแต่มันไม่เป็นโค้ดของพวก professional ทำกัน และโปรแกรมของเราจะช้าลงแบบไม่ควรจะเป็น

ถ้าเราต้องการต่อ string แบบไม่ได้ซับซ้อนอะไรแนะนำให้ใช้ Placeholder หรือไม่ก็ Interpolation แทนการใช้เครื่องหมาย + (ถ้าไม่รู้ว่ามันคืออะไรให้กลับไปดูบทก่อนหน้าซะ)

👨‍🚀 การต่อ string ที่ซับซ้อน

ให้ใช้คลาส StringBuilder เข้ามาช่วย จากตัวอย่างจะเห็นการใช้ CPU และความเร็วที่แตกต่างกันอย่างเห็นได้ชัดเลย (กดๆดูไปเถอะอุตส่าทำมาให้ดู + ไหนๆกดดูแล้วฝากกด subscribe กดไลค์กดแชร์ด้วยเน่อ)

18.มารู้จักกับ Constructor กันบ้าง

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

🎯 สรุปสั้นๆ

👨‍🚀 Constructor

มีหน้าที่กำหนดข้อมูลพื้นฐานให้กับตัวแปรต่างๆของคลาสนั้นๆ โดยมันจะมีชื่อเดียวกับคลาสของเราเป๊ะๆเลย ตามโค้ดด้านล่าง (บรรทัดที่ 3~5 นั่นแหละเจ้า constructor)

แล้วถ้าเราอยากกำหนดค่าพื้นฐานให้มันล่ะ? ก็ทำตามตัวอย่างด้านล่างได้เบย

ทุกครั้งที่เราสร้าง object ใหม่จากคลาส Student เราจะได้ตัวแปร Name มีค่าเป็น Unknow เสมอ

  • Constructor สามารถมี parameter ได้เหมือน method เบย เพียงแค่มันไม่มี return type เท่านั้นเอง

  • ภายในคลาส 1 คลาส สามารถมี Constructor ได้มากกว่า 1 ตัว แต่ว่า แต่ละตัวจะต้องรับ parameter ที่ไม่เหมือนกันเลยนะ

Default constructor โดยปรกติเวลาที่เราสร้างคลาสมา แม้ว่าเราจะไม่เขียน constructor ไว้ แต่เวลาที่มันเอาไป compile มันจะแอบเติม constructor ที่ไม่รับ parameter ขึ้นมา 1 ตัวเสมอ ซึ่งเจ้า constructor แบบนี้เราเรียกมันว่า default constructor แต่ในทางกลับด้านกันถ้าเรามี constructor อยู่แล้ว โปรแกรมมันจะไม่ใส่ default constructor ให้นะกั๊ฟ (ขอแค่มีแค่ 1 ตัวมันก็ไม่ใส่ให้ละ)

11.การทำงานซ้ำๆด้วย Do While

💬 หลังจากที่เราเห็นตัวช่วยในการจัดการกับงานที่ทำงานซ้ำๆหลายๆรอบไปละ ในรอบนี้เราลองมาดูการเขียน Do..While loop statements กันดูบ้างนะครับว่ามันจะต่างกับ While loop statements ยังไงบ้าง

🎥 ตัวอย่างการใช้คำสั่ง Do While loop statements

🎯 สรุปสั้นๆ

👨‍🚀 คำสั่ง Do While มาตรฐาน

คำสั่ง do while จะต่างกับ while ธรรมดาตรงที่ do while จะยอมปล่อยให้เข้าไปทำในวงเล็บ { } ก่อน 1 รอบ แล้วค่อยตรวจสอบเงื่อนไขว่ามันจะได้กลับมาทำหรือเปล่า ซึ่งในขณะที่ while loop จะไม่ยอมให้เข้าไปทำในวงเล็บเลยถ้าเงื่อนไขไม่ถูกต้อง

  • do while > มันให้ลองเล่นก่อน 1 รอบ แล้วค่อยดูเงื่อนไข

  • while > ตรวจเงื่อนไขก่อนค่อยให้เล่น

21.ลองใช้คลาสแบบจริงจังบ้าง

💬 ตั้งแต่บทที่ 16~20 เราได้เห็นทฤษฎีของคลาสกันมาเยอะพอสมควรละ คราวนี้เราจะมาลองใช้คลาสแบบจริงๆจังๆดูบ้างว่ามันจะออกมาเป็นแบบที่คิดไว้หรือเปล่านะ

🎥 ใช้คลาสเขียนเกมเป่ายิงฉุบ

🎯 สรุปสั้นๆ

👨‍🚀 การใช้งานคลาส

จะเห็นว่าเวลาที่ผมสร้างคลาสขึ้นมาซักตัว ผมจะมองก่อนว่ามันมีอะไรที่จำเป็นต้องใช้ในคลาสนั้นๆบ้างแล้วผมค่อยเอาไปใส่คลาสนั้นๆ ส่วนเวลาจะสร้าง method ผมก็จะมองว่าคลาสนั้นมันต้องทำอะไรได้บ้างและความสามารถนั้นหรือ method นั้นควรอยู่กับคลาสไหน แล้วค่อยสร้าง method ให้กับคลาสที่มันควรจะอยู่ แล้วหลังจากที่มันทำงานได้หมดละ ผมค่อยแก้ไขโค้ดให้มันกระชับขึ้นอีกทีครับ หรือเราเรียกว่าการทำ Refactor Code

Refactor Code คือการจัดการให้โค้ดของเรา อ่านง่าย สามารถจัดการได้ง่ายขึ้น หรือคำศัพท์ที่เราใช้เป็นประจำคือ การทำให้มันสามารถ maintenance ได้ง่ายขึ้นครับ ซึ่งเรื่องการ Refactor Code แบบลงลึกๆ ผมจะขอแบ่งเอาไว้อธิบายในคอร์ส Clean Code แทนนะครับ

⏳ระดับสูง

ระดับคนทำเป็นอาชีพควรจะต้องรู้

ความรู้สำหรับคนที่ต้องการจะยึดเอา C# ไปใช้ทำมาหากินและเป็นมือโปรควรจะต้องรู้ แม้ว่าจะไม่รู้ของพวกนี้เลยก็สามารถเขียนแอพเจ๋งๆออกมาได้เหมือนกันนะ แต่การที่เรารู้เรื่องเหล่านี้จะทำให้เราประหยัดเวลาชีวิตได้เยอะขึ้นมากเบย 😍

อย่าพึ่งใจใจร้อนนะโยมนะ กำลังค่อยๆอัพเดทคอร์สนี้อยู่ น่าจะเสร็จราวๆวันที่ 31/10/2019 นี้แหละ

GenericDelegatesAction & FuncLambda expressionLINQพระคัมภีร์การใช้คำสั่ง LINQ
  • Async & Await

  • Threading

  • Stream

  • เดี๋ยวค่อยมาคิดต่อละกัน ผักชีโรยหน้าเท่านี้ก่อนนะจุ๊

สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

เขียนโค้ดด้วยภาษา C#

ตำราพิชัยสงครามตั้งแต่ไม้จิ้มฟันยันยาวอวกาศ !

👑 สิ่งที่ควรรู้ของภาษา C#

  • C# อ่านว่า ซีชาป นะจ๊ะเบบี๋

เกิดมาไม่เคยเขียนโค้ดมาก่อนเบย

ทุกอย่างมันต้องมีครั้งแรก ไม่ต้องเป็นห่วง ปลาตัวนี้นิ่มไร้ก้าง ไร้เกล็ด ไร้ขน ... นี่ก็เริ่มหวั่นๆละว่ามันเป็นปลาหรือเปล่า

💬 สำหรับอุ๊งๆหน้าใหม่ที่ไม่เคยจับปลามาก่อนในชีวิต ดช.แมวน้ำ อยากเคลียเรื่อง การเขียนโปรแกรม ให้เข้าใจตรงกันก่อนว่ามันคืออะไร

เคลียคำถามกันโหน่ย

10.การทำงานซ้ำๆด้วย While

💬 ในการเขียนโปรแกรมหลายๆ บางทีเราก็จะพบว่าโค้ดบางส่วนที่เราอยากให้มันทำงานหลายๆรอบ เราต้องไปคอยนั่งกดก๊อปปี้ไปวางหลายๆครั้งใช่มุ้ยล๊า ในรอบนี้เราจะลองดูคำสั่งที่ใช้ในการทำงานเดิมซ้ำๆกันที่เราเรียกมันว่า While loop statements นั่นเอง

🎥 ตัวอย่างการใช้คำสั่ง While loop statements

Abstraction & Encapsulation

หลังจากที่เข้าใจหลักการของ Abstraction กับ Encapsulation กันไปเรียบร้อยแล้ว ดังนั้นเรามาสรุปความเข้าใจเจ้า 2 ตัวนี้ก่อนว่ามันเกี่ยวเนื่องกันยังไง

🔥 Abstraction

เป็นการแปลงโจทย์ให้เป็น Models เพื่อให้เราเอาไปเขียนโค้ดต่อ

24.Abstract Class

💬 เวลาที่เราสร้าง Base class ในบางทีเราอาจจะรู้แค่ มันน่าจะมี method ตัวนี้ไว้นะ แต่ไม่รู้ว่ามันต้องทำงานยังไง เพราะการทำงานของ method นั้นใน Derived Class แต่ละตัวทำงานไม่เหมือนกันเลย ก็เป็นไปได้ หรือในบางทีเราอยากสร้างคลาสที่มีหน้าที่เป็น Base Class เท่านั้นและห้ามนำมันมาใช้สร้าง object ล่ะ? ซึ่งจากคำถามที่ว่ามาทั้งหมดเจ้าตัวที่ชื่อว่า Abstract Class จะมาช่วยเราแก้ปัญหาในจุดนี้ขอรับ

🎯 สรุปสั้นๆ

1.โปรแกรมที่ต้องลง

💬 ในการเขียนภาษา C# เราจะต้องติดตั้งโปรแกรมนิดโหน่ย เพื่อให้เครื่องคอมเราทำงานด้วยได้นะฮ๊าฟ ซึ่งโปรแกรมที่ต้องติดตั้ง ดช.แมวน้ำ ขอแนะนำ 2 ตัวนี้นะ (เลือกแค่ตัวเดียวก็พอนะ แต่ถ้าอยากลงทั้งคู่ก็ไม่เป็นไรแค่เปลืองพื้นที่เจ๋ยๆ)

Visual Studio Community

เหมาะสำหรับคนที่ใช้งานจริงจัง ลงตัวนี้ตัวเดียวจบเลย ใช้งานง่ายไม่ต้องใช้ Command line โหลดจากลิงค์ด้านล่างนี้

2020-09

✏️ ประวัติการอัพเดท

สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

15.Value type vs Reference type

💬 ในรอบนี้เราจะมาทำความรู้จักกับชนิดของข้อมูลของเราให้ลึกซึ้งยิ่งๆขึ้นไปอีกขั้นนะฮ๊าฟ ซึ่งเวลาที่เรากำหนดชนิดของข้อมูล int, double, string, bool อะไรพวกนี้ จริงๆในเบื้องลึกเขาแบ่งกลุ่มของพวกนี้ไว้ทั้ง 2 แบบนะครับนั่นคือ Value type กับ Reference type ส่วนมันคืออะไรและต่างกันยังไง ลองกดดูวีดีโอกันเบย

🎯 สรุปสั้นๆ

Boxing & Unboxing

ความรู้เรื่องนี้เป็นความรู้ประดับ ไม่รู้ก็ทำงานได้ไม่มีปัญหาอะไร แต่ถ้ารู้จะทำให้ performance ของโค้ดเราดีขึ้น ละเข้าใกล้ Production Code มาขึ้นครัช

Boxing

ปรกติตัวแปรที่มี Data type เป็น object นั้นจะสามารถเก็บทุกสิ่งทุกอย่างใน C# ได้ เพราะมันคือต้นตระกูลนั่นเอง เช่น ถ้าเราอยากเก็บค่าตัวเลขไว้ใน object เราก็เขียนแบบนี้ก็ได้

🧓 Uncle Bob - TDD
🧓 Uncle Bob - Mindset
🧓 Uncle Bob - Naming
🧓 Uncle Bob - Comments
🧓 Uncle Bob - Intro
👦 Communication Patterns
👽 Algorithm P & NP
👾 Algorithm Part 2
👶 Data Structure & Algorithm
Facebook Blog: Mr.Saladpuk

ช่วงนี้งานของ ดช.แมวน้ำ เยอะม๊วก เลยไม่ค่อยได้อัพเดทบทความเท่าไหร่นะจ๊ะ แต่จะค่อยๆทยอยลงเรื่อยๆเมื่อมีโอกาศ ส่วนใครที่ด่วนอยากได้ความรู้ตรงไหนก็ทัก Facebook ส่วนตัวมาได้นะกั๊ฟ

25/09/2020

  • เพิ่มบทความ 🔐 HTTPS in a nutshell - อธิบาย HTTPS แบบละเอียดยิบในภาษาชาวบ้าน

20/09/2020

  • เพิ่มบทความ 😎 The Matrix 2 - สรุปปมหนังในแง่มุมของ Computer Security กันโหน่ย

18/09/2020

  • เพิ่มบทความ 😎 The Matrix 1 - สรุปปมหนังในแง่มุมของ Computer Security กันโหน่ย

Facebook Blog: Mr.Saladpuk
บริษัทที่พัฒนาคือ Microsoft ที่เราใช้ Windows กันอยู่ทุกวันนี้แหละ
  • ภาษานี้พัฒนาจากภาษา Java แล้วปรับปรุงให้ดีกว่าเดิมเยอะมาก ดังนั้นถ้าเข้าใจภาษานี้ปุ๊ป แทบจะเรียกว่าโดดไปเขียน Java ได้เลย (ภาษา Java หยุดพัฒนาไปนานม๊วกเลยขอเลือก C# มาสอนแทน อวยๆ)

  • สามารถใช้ภาษานี้เขียนแอพได้เกือบทุกชนิดที่สุดแล้วซึ่งภาษาส่วนใหญ่จะทำไม่ได้ เช่น เขียนเว็บ, แอพมือถือ, แอพบน Windows, Mac OS, Linux, เขียนเกม บลาๆขี้เกียจไล่ละ

  • 🎥 ทำไมเราควรเรียนภาษา C#

    🧭 เนื้อหาทั้งหมดของคอร์สนี้

    👶 พื้นฐาน

    🧑 ระดับกลาง

    👨 ระดับสูง

    ระดับนี้กำลังค่อยๆเขียนอยู่ น่าจะเสร็จราวๆวันที่ 30/09/2019 นี้แหละ

    1.โปรแกรมที่ต้องลง
    2.โครงสร้างของโค้ด
    3.ชนิดของข้อมูล
    4.การสร้างตัวแปร
    5.คำสั่งพื้นฐาน
    6.การแปลงข้อมูล
    7.การเปรียบเทียบค่า
    8.การตัดสินใจด้วย IF statements
    9.การตัดสินใจด้วย Switch statements
    10.การทำงานซ้ำๆด้วย While
    11.การทำงานซ้ำๆด้วย Do While
    12.การทำงานซ้ำๆด้วย For
    13.การแก้โจทย์จากรูป
    14.มารู้จักกับ Array กัน
    15.Value type vs Reference type
    16.ลดงานซ้ำๆด้วย Method
    17.มารู้จักกับ Class & Field กัน
    18.มารู้จักกับ Constructor กันบ้าง
    19.มาเขียน Method ใน Class กัน
    20.มารู้จักกับ Property กัน
    21.ลองใช้คลาสแบบจริงจังบ้าง
    22.การสืบทอด Inheritance
    23.Polymorphism
    24.Abstract Class
    25.Interface
    26.Namespace
    27.Enum
    28.Exception handler
    29.ลงลึกกับ string
    30.StringBuilder เพื่อนคู่ string
    🤔 การเขียนโปรแกรมคืออะไร ?

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

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

    🤔 แล้วเราจะเขียนโปรแกรมไปทำไม ?

    นั่นดิ! ส่วนใหญ่ก็โดนบังคับมาเรียนนิเลยต้องมานั่งอ่านอยู่นี่ไง!! 🤣 (ใจเย็นนะโยม นะ)

    ดช.แมวน้ำ ขออธิบายแบบนี้ละกัน 💰 ไงละ! สายอาชีพนี้เป็นหนึ่งในกลุ่มที่ขาดตลาดพอๆกับหมอเลยนะจุ๊ และค่าแรงก็เหยียบหลักแสนอีกด้วย (ใจจริงก็ไม่อยากจะบอกแบบนั้นหรอกเด็กๆอ่านอยู่เยอะเลยได้แต่คิดในใจเอา เลยขอตอบหล่อๆว่า) การฝึกเขียนโปรแกรมจะทำให้เราฉลาดขึ้น เพราะมันจะทำให้เราคิดแบบมีเหตุมีผล (ตอบเหมือนเมากาวเลยเน๊อะ แต่เชื่อเถอะ)

    ค่าตัวโปรแกรมเมอร์ในกรุงเทพจะอยู่ประมาณ 90k~200k บาท ส่วนในต่างจังหวัดจะอยู่ 20k~70k บาท

    • 1k = 1,000 บาท

    🤔 จะเขียนโปรแกรมต้องมีอะไรบ้าง ?

    คอมไงนู๋! เอ็งจะเขียนใส่ฝาบ้านหร๋า? มีคอมอย่างเดียวไม่พอนะ ต้องมีเงินจ่ายค่าไฟด้วย 🙃

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

    🤔 ภาษาโปรแกรมมีเยอะม๊วกแล้วจะเรียนภาษาไหนดี ?

    เอาภาษาไทยกับอังกฤษให้รอดก่อนไหม๊ ? 🙃

    ดช.แมวน้ำ อยากพูดจากใจจริงว่า เลือกภาษาไหนก็ได้ขอแค่มีคนช่วยสอนช่วยอธิบายก็พอ เพราะจริงๆภาษาโปรแกรมมันไม่ต่างกันเท่าไหร่หรอก (เชื่อเต๊อะ) ขอแค่เรารู้จริงรู้ลึกแค่ภาษาเดียวก่อน แล้วเราอยากจะเปลี่ยนไปเรียนภาษาอื่นมันจะใช้เวลาไม่ถึงเดือนหรอก รูปด้านล่างคือภาษาโปรแกรมที่นิยมใช้กันในปี 2017 ~ 2018

    โดยส่วนตัว ดช.แมวน้ำ อยากแนะนำภาษา C# เพราะเว็บนี้สอน C# งุย อีกสาเหตุคือภาษา C# มันใกล้เคียงกับภาษา Java ดังนั้นเข้าใจ C# ปุ๊ปก็เหมือนกับเราจะได้ภาษา Java ไปในตัวด้วย

    https://spectrum.ieee.org/at-work/innovation/the-2018-top-programming-languages

    🤔 แต่ละภาษามันต่างกันตรงไหน ?

    ดช.แมวน้ำ ขอแยกตอบเป็น 2 เรื่องละกันเน่อ คือถ้าพูดถึง แก่นแท้ (core concept) เช่นการตรวจสอบเงื่อนไข หรือการทำซ้ำต่างๆแล้วละก็แทบจะไม่ต่างกันเท่าไหร่หรอก แต่ถ้าพูดถึงว่าแต่ละภาษามันเขียนอะไรได้บ้าง อันนี้จะขึ้นอยู่กับแต่ละภาษาเบย

    จากรูปด้านบนภาษา PHP จะเขียนเว็บไซต์ได้อย่างเดียว ในขณะที่ JavaScript สามารถเขียนเว็บไซต์ได้และสามารถเขียนแอพมือถือได้ด้วย ดังนั้นเวลาจะเขียนแอพอะไรก็ตาม เราก็ต้องดูด้วยว่าภาษาที่เราใช้มันเขียนของพวกนั้นได้หรือเปล่าด้วยนะจ๊ะ ไม่งั้นมันอาจจะเป็นการเอาค้อนไปเลื่อยไม้ก็ได้นะ 😑

    🎯 สรุปสั้นๆ

    👨‍🚀 คำสั่ง while loop พื้นฐาน

    👨‍🚀 คำสั่ง break

    เมื่อเราทำงานอยู่ใน loop ในบางครั้งเราก็ไม่อยากให้มันทำงานต่อแล้ว เราสามารถใช้คำสั่ง break เพื่อออกจาก loop ที่กำลังทำงานอยู่ได้

    while( เงื่อนไข )
    {
      // ถ้าเงื่อนไขเป็นจริงจะทำในวงเล็บนี้เรื่อยๆ
    }
    🔥 Encapsulation

    เป็นการ ควบคุมสิทธิ์ และทำให้ของต่างๆทำงานร่วมกัน

    https://www.scientecheasy.com/2018/06/encapsulation-in-java-real-time-examples-advantages.html

    💖 Abstraction + Encapsulation

    เมื่อเราเอาทั้ง 2 แนวคิดนี้มารวมกัน เราจะได้ Models ที่มีการควบคุมสิทธิ์ในการเข้าใช้งาน นั่นเอง ซึ่งประโยชน์ที่เราจะได้ก็คือ Model ของเราจะเป็น Component ที่สามารถเอาไปใช้งานได้ โดยที่คนอื่นไม่จำเป็นต้องสนใจเลยว่าจริงๆมันทำงานยังไง ยกตัวอย่างเช่น ทีวี

    iconarchive.com

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

    เมื่อมองย้อนกลับมาเราจะเห็นว่าทีวีมันมีความเป็น Abstraction ก็คือเราสามารถใช้งานมันได้โดยที่ไม่จำเป็นต้องรู้ว่าภายในมันทำงานยังไง และในแง่ของ Encapsulation มันก็ซ่อนวงจรต่างๆไว้ภายในไม่ให้เราต้องไปรับรู้ แต่ถ้าเราอยากเชื่อมทีวีกับของอย่างอื่น เช่นลำโพง เขาก็จะมีช่องด้านหลังทีวีให้เราไปจิ้มใช้งานกับเขาได้เลย เพราะเขาเปิดมันให้เราใช้งานได้แค่นั้น ส่วนในแง่ของ Component นั้นจะเห็นว่าทีวีที่มันสมบูรณ์แบบในตัวมันเอง เรามีหน้าที่ใช้มันก็พอ ดังนั้นเราก็สามารถนำทีวี ไปใช้งานร่วมกับ Component อื่น เช่น ต่อ AppleTv ต่อลำโพง ต่อเครื่องเกม Nintendo, Xbox, PS4 เข้าไปก็จะสามารถทำงานร่วมกันของอื่นๆได้ทันทีเลย

    🎯 บทสรุป

    Models ที่ดีจะต้องมีการทำ Abstraction เพื่อให้มันใช้งานได้ง่าย แต่จะต้องคิดถึง Sensitive Data ด้วย ดังนั้นเราก็ต้องนำหลักของ Encapsulation เข้ามาร่วมในการออกแบบด้วยนั่นเอง

    👨‍🚀 Abstract Class คือ

    แม่แบบของคลาส หรือ Template class นั่นเอง มีแค่โครงสร้างต่างๆไว้ให้ ส่วนคลาสลูกมีหน้าที่ไปกำหนดเอาเองว่าของด้านในจริงๆจะเป็นยังไง แต่ในขณะเดียวกันมันก็มี method ที่สมบูรณ์ในนั้นได้ด้วยนะ

    Abstract class 1.เราไม่สามารถสร้าง object จาก abstract class ได้นะ 2.คลาสที่สืบทอด (inheritance) จาก abstract class ไปจะต้องทำการ implement abstract methods ทุกตัวทันทีด้วย 3.ถ้า abstract class ทำการ inheritance จาก abstract ด้วยกัน จะยังไม่ทำการ implement abstract method ก็ได้นะจุ๊

    ตัวอย่างการสร้าง Abstract class โดยให้คลาสลูกเป็นคนกำหนดว่าการคำนวณพื้นที่ของรูปแบบแต่ละอย่างเป็นยังไง

    public abstract class Shape
    {
       public abstract double GetArea();
    }
    
    public class Circle : Shape
    {
       public double Radius { get; set; }
       
       public override double GetArea()
       {
          return 3.141 * Radius * Radius;
       }
    }
    
    public class Triangle : Shape
    {
       public double Width { get; set; }
       public double Height { get; set; } 
       
       public override double GetArea()
       {
          return 0.5 * Width * Height;
       }
    }

    คนที่จะลงตัวนี้ลองเช็คเครื่องตัวเองดูก่อนนะว่า มีพื้นที่เหลือ 20 GB ขึ้นไป และเครื่องไม่ช้า ส่วน Ram ขั้นต่ำคือ 4 GB แต่แนะนำว่า 8 GB จะดีมาก แต่ถ้าไม่สนใจคำเตือนอยากลองลงดูก็ได้นะ เครื่องไม่พังหรอกแต่มันอาจจะอืดๆหน่อยนะจุ๊

    Visual Studio Code

    อันนี้เป็นรุ่นน้องเล็ก เหมาะสำหรับคนที่อยากลองเขียนโปรแกรมใหม่ๆ แต่ต้องติดตั้งของ 2 อย่างคือ Visual Studio Code และ .NET Core SDK ด้วย

    โปรแกรม

    ลิงค์ดาวโหลด

    Visual Studio Code

    .NET Core SDK

    ข้อดีของตัวนี้คือไฟล์ไม่ใหญ่ คอมไม่ต้องเร็วมากก็ทำงานได้ไม่มีปัญหา แต่ข้อเสียคือต้องใช้ Command line หน่อยนึงนะจ๊ะ

    .NET In-Browser

    อันนี้แถมนะจ๊ะ สำหรับคนที่ขี้เกียจลงทุกๆอย่างที่ว่ามา แล้วอยากลองวิชาเลย สามารถเข้าไปลองใช้ C# ผ่านเว็บไซต์ด้านล่างนี้ได้เบย

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

    https://dotnet.microsoft.com/learn/dotnet/in-browser-tutorial/1

    https://visualstudio.microsoft.com/vs/community
    👨‍🚀 Value type

    ชนิดข้อมูลพื้นฐานที่เรานิยมใช้กันเช่น int, double, bool อะไรพวกนี้อยู่ในกลุ่มของ value type นะครับ ซึ่งลักษณะเฉพาะตัวของกลุ่มนี้คือ ตัวแปรแต่ละตัวเวลามันเก็บข้อมูลมันจะเก็บแยกของใครของมัน แยกขาดจากกันเลย ไม่เกี่ยวข้องกันเลย

    👨‍🚀 Reference type

    ชนิดข้อมูลส่วนใหญ่ที่อยู่ในกลุ่มนี้จะเป็นพวก class ต่างๆและรวมถึง string ด้วย ซึ่งลักษณะเฉพาะตัวของกลุ่มนี้คือ ตัวแปรแต่ละตัวมันจะไม่เก็บข้อมูลไว้ แต่มันใช้การชี้ไปยังข้อมูลแทน ซึ่งตัวแปรต่างกันก็สามารถชี้ไปที่ข้อมูลตัวเดียวกันได้ ดังนั้นเวลาเวลามีการแก้ไขข้อมูลมันก็จะทำให้ตัวแปรที่ชี้มาหาข้อมูลเดียวกันมีผลกระทบไปด้วย

    Immutable of string หลายคนพอได้ยินว่า string นั้นอยู่ในกลุ่มของ Reference type ก็เลยไปลองเล่นดู แต่พอลองดูจะพบว่าพฤติกรรมของมันจะเหมือนกับ value type มากกว่า เพราะเวลาให้ตัวแปรอื่นไปแก้ไขข้อมูลจะพบว่าอีกตัวแปรนึงไม่เปลี่ยนตาม นั่นก็เพราะ ตอนที่เราไปแก้ไขข้อมูลของ string จริงๆนั้นเราทำไม่ได้ เพราะ string มีลักษณะเฉพาะตัวอีกข้อคือสิ่งที่เรียกว่า Immutable ซึ่งความหมายของ immutable ผมขอยกไปอยู่ในบทของ string อีกทีละกันครับ

    ซึ่งจากโค้ดด้านบน การทำงานจริงๆของมันคือ มันจะไปสร้าง object ใหม่ให้กับตัวแปร o แล้วมันจะ Copy ค่าตัวแปร i ไปเก็บไว้ใน heap แล้วค่อยให้ตัวแปร o ชี้ไปหาตัวแปรที่ copy ขึ้นมาต่ออีกที

    จากที่ร่ายมากระบวนการนี้เราเรียกมันว่าการทำ Boxing นั่นเอง ซึ่งเกิดขึ้นภายในบรรทัดที่ 2

    Unboxing

    คราวนี้ตัวแปรที่เราเก็บไว้ใน object เมื่อเราต้องการเอาออกมาใช้งาน ให้กลับมาอยู่ใน Data type ปรกติของมัน เราก็จะต้องทำการ cast มันกลับไปนั่นเองตามโค้ดด้านล่าง

    จากโค้ดด้านบน บรรทัดที่ 3 โปรแกรมจะต้องไปเอาค่าที่เก็บในตัวแปร o ออกมา แล้วแปลง data type ให้กลับคืนมาเป็น int นั่นเอง

    ซึ่งกระบวนการทำ Unboxing นี้อาจจะเกิด error ขึ้นได้ ถ้าข้อมูลที่เก็บอยู่มันไม่สามารถ cast กลับออกมาได้นั่นเอง

    int i = 123;
    object o = i;     // boxing
    Microsoft: What's new in C# 8.0
    Microsoft: Default interface members versions
    public class Student
    {
       public Student()
       {
       }
    }
    do
    {
      // Do something
    } while( เงื่อนไข );
    29.ลงลึกกับ string

    2020-02

    สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก Facebook Blog: Mr.Saladpuk ได้นะครับ 😍

    ช่วงนี้งานของ ดช.แมวน้ำ เยอะม๊วก เลยไม่ค่อยได้อัพเดทบทความเท่าไหร่นะจ๊ะ แต่จะค่อยๆทยอยลงเรื่อยๆเมื่อมีโอกาศ ส่วนใครที่ด่วนอยากได้ความรู้ตรงไหนก็ทัก Facebook ส่วนตัวมาได้นะกั๊ฟ

    23/02/2020

    • เพิ่มคอร์สใหม่

    22/02/2020

    • อัพเดทคอร์ส เรื่อง

    21/02/2020

    • อัพเดทเกร็ดความรู้เรื่อง

    • สร้างคอร์ส ใครยังไม่รู้เรื่อง database เลยรีบมาดูด่วน

    20/02/2020

    • อัพเดทเกร็ดความรู้เรื่อง

    17/02/2020

    • อัพเดทเกร็ดความรู้เรื่อง

    16/02/2020

    • อัพเดทเกร็ดความรู้เรื่อง

    15/02/2020

    • อัพเดทเกร็ดความรู้เรื่อง

    • อัพเดทเกร็ดความรู้

    13/02/2020

    • อัพเดท เรื่อง

    10/02/2020

    • อัพเดท เรื่อง

    09/02/2020

    • อัพเดท เรื่อง

    08/02/2020

    • อัพเดท เรื่อง

    • ตั้งคอร์ส เพื่อใช้เป็นแนวทางในการสร้างคลาว์โปรเจคในระดับมาตรฐาน เพื่อป้องกันไม่ให้ความลับรั่ว

    07/02/2020

    • อัพเดทซีรี่ Saladpuk Games เรื่อง

    Delegates

    😢 ปัญหา

    เวลาที่เราเขียนโค้ด แล้วอยากเก็บข้อมูลต่างๆ เช่น ตัวเลข ตัวอักษร อะไรพวกนี้เราก็สามารถสร้างตัวแปรมาเก็บไว้ได้ง่ายๆใช่ป่ะ ตามโค้ดด้านล่างเลย

    แต่ถ้าเราอยากเก็บ Method ไว้ในตัวแปรบ้างล่ะ เราจะทำยังไง?

    ข้อแนะนำ เรื่อง delegate ในปัจจุบันเราแทบไม่ได้ยุงกับมันเท่าไหร่อยู่แล้ว เพราะ C# ได้พัฒนาต่อยอดความสามารถเจ้า delegate ออกมาเยอะละ ดังนั้นปรกติเราจะใช้ตัวอื่นแทนนั่นเอง แต่สาเหตุที่ต้องเขียนเรื่องนี้ก็เพราะ เจ้า delegate นี้คือหัวใจหลักของความสามารถอื่นๆที่เราจะเรียกใช้มันยังไงล่ะ

    😄 วิธีแก้ปัญหา

    ในภาษา C# เรามีตัวที่ชื่อว่า Delegate เอาไว้แก้ปัญหานี้อยู่ ซึ่งการทำงานของมันอธิบายไปตรงนี้เดี๋ยวจะ งง เปล่าๆ ลองไปดูโค้ดจะเข้าใจง่ายกว่า

    สมมุติว่าเรามี method อยู่ตัวนึง ตามโค้ดด้านล่าง

    แล้วเราอยากจะเก็บมันไว้ในตัวแปรซักตัว สิ่งที่เราต้องทำก็คือ สร้าง delegate ขึ้นมาก่อน โดยการสร้างเจ้าตัวนี้ เราจะต้องบอกว่า method ของเรามี Signature เป็นแบบไหน ซึ่งจาก โค้ดด้านบน เราจะเห็นว่า Signature ของมันคือ

    1. มันมี return type เป็น void

    2. มันไม่รับ parameter อะไรเลย

    จากที่ว่ามาเราก็จะสร้างเจ้า delegate ขึ้นมาก่อน ตามนี้

    ตรง YOUR_DELEGATE_NAME คือชื่อของ delegate ที่เราสร้างขึ้นมา จะเป็นชื่ออะไรก็ได้ ในตัวอย่างของใช้ชื่อนี้เลยละกันขี้เกียจแก้ละ

    ถัดมาเราก็จะสร้างตัวแปร เพื่อเอามาเก็บเจ้า method Saladpuk() ของเรา ดังนั้นก็จะออกมาราวๆนี้

    สุดท้ายเราก็จะลองเรียกใช้งานมันดู

    ผลลัพท์ Hello world

    ถ้าตัวตัว method ของเรารับ parameter หรือมีการส่งค่ากลับเป็นอย่างอื่นที่ไม่ใช้ void แล้วล่ะก็ เจ้าตัว delegate ของเราก็จะต้องมี signature ที่ตรงกับมันด้วยนะถึงจะใช้งานได้

    📝 Source Code

    🎯 บทสรุป

    เจ้าตัวนี้ในปัจจุบันเราไม่ได้ไปยุ่งกับมันเท่าไหร่แล้ว แต่เราต้องเข้าใจมันนิดหน่อย เพราะในเรื่องอื่นๆของ C# ได้ใช้หลักเจ้าตัวนี้เยอะอยู่ เช่นพวก Action, Func, Lambda ในบทถัดไปนั่นเอง

    12.การทำงานซ้ำๆด้วย For

    💬 ในหลายๆครั้งที่งานเราต้องทำซ้ำๆกันเดิม แต่มันอาจจะต้องมีการสร้างตัวแปรมาใช้ด้วย ซึ่งมันก็ใช้เพียงแค่ที่เดียว (คือใช้แล้วทิ้งตรงนั้นเลย) ทำให้เราเสียเวลาต้องคอยดูตัวแปรพวกนั้นด้วย ดังนั้นในบทนี้เราจะมาดูการทำงานซ้ำๆโดยใช้คำสั่ง For loop statement กันบ้างนะครับ

    🎯 สรุปสั้นๆ

    👨‍🚀 คำสั่ง For loop มาตรฐาน

    การเขียน for ไม่จำเป็นต้องมี INITIALIZER ก็ได้นะ หรืออาจจะมีมากกว่า 1 ตัวก็ได้ เช่นโค้ดตัวอย่างด้านล่าง

    เช่นเดียวกันตัว ITERATOR ก็ไม่จำเป็นต้องมีหรือจะมีกกว่า 1 อย่างก็ได้ เช่นโค้ดด้านล่าง

    และสุดท้าย CONDITION จะไม่ใส่ก็ได้ มันก็จะมองเป็น infinity loop ทันที

    เกร็ดความรู้ Infinity loop คือการทำงานในวงเล็บนั้นๆซ้ำไปเรื่อยๆไม่ไม่ทางจบการทำงาน ซึ่งสมัยโบราณเรานิยม loop ประเภทนี้ทำงานประเภท งานที่ต้องทำงานตลอดเวลา 24 ชม ไม่มีการหยุดพักเลย แต่ในปัจจุบันเราไม่นิยมเขียนโค้ดแบบนั้นแล้ว ซึ่งเราจะใช้ความสามารถของ cloud เข้ามาช่วย ซึ่งประสิทธิภาพจะดีกว่าการเขียนเป็น infinity loop มากครับ

    6.การแปลงข้อมูล

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

    🎯 สรุปสั้นๆ

    👨‍🚀 การแปลงข้อมูลแบบอัตโนมัติ (Implicit conversions)

    เราไม่ต้องทำอะไรเลย เพราะโปรแกรมจะจัดการให้อัตโนมัติ เช่น เราสามารถแปลง int เป็น double ได้ตามตัวอย่างด้านล่างเบย

    👨‍🚀 การแปลงข้อมูลด้วยตัวเอง (Explicit conversion)

    กรณีที่โปรแกรมจัดการให้เราอัตโนมัติไม่ได้ เราจะต้องทำการระบุ data type ที่จะทำการแปลงลงไปด้วย หรือเรียกว่าการ cast เช่น เราทำการแปลง double เป็น int แบบตัวอย่างด้างล่างงุย

    ข้อควรระวังในการทำ Explicit conversion

    • ในบางทีการแปลงข้อมูลอาจจะทำให้ข้อมูลบางอย่างหายไปได้ เช่นในตัวอย่าง มันจะตัดทศนิยมออกไป ดังนั้น b จะมีค่าเป็น 3

    • ถ้ามันไม่สามารถแปลงได้ โปรแกรมจะพังทันที (เราเรียกกรณีนี้ว่าเกิด exception)

    👨‍🚀 การแปลงโดยใช้ตัวช่วย

    1.ตัวช่วยในการแปลงข้อมูลเรานิยมใน System.Convert ตามตารางด้านล่าง

    2.การแปลงข้อมูลจาก string เป็น data type ที่ระบุโดยใช้ตัวช่วย

    👨‍🚀 การแปลงข้อมูลเป็น string

    เรานิยมใช้คำสั่ง .ToString() ต่อท้าย เพื่อทำการแปลงข้อมูลนั้นๆให้กลายเป็น string ตามตัวอย่างด้านล่าง

    19.มาเขียน Method ใน Class กัน

    💬 อะเช หลังจากที่รู้เรื่องคลาสมาพอประมาณละ เราก็จะเห็นว่ามันใส่ตัวแปรต่างๆเข้าไปในคลาสได้ชิมิ ดังนั้นเพื่อนๆก็น่าจะรู้แล้วว่ามันก็น่าจะใส่ method ลงไปได้เหมือนกัน แต่ method ที่อยู่ในคลาสมันมีลูกเล่นอะไรกันบ้างนะไปดูกันเบย

    ขออภัย ในวีดีโอผมพึ่งเห็นว่าผมเมาอธิบายเรื่อง method overloading ว่า return type ไม่เกี่ยว จริงๆคือ เกี่ยวนะครับ

    🎯 สรุปสั้นๆ

    👨‍🚀 Method overloading

    คือการสร้าง method ที่มีชื่อเดียวกันและอยู่ภายในคลาสเดียวกัน โดยมันจะมีกฎที่ให้เราทำแบบนั้นได้คือ method พวกนั้นจะต้องมี parameter ที่ไม่เหมือนกัน หรือ มี return type ต่างกัน

    parameter ไม่เหมือนกัน เช่น ลำดับของ data type ไม่เหมือนกัน หรือ จำนวนของ parameter ไม่เท่ากัน (ชื่อของ parameter ไม่เกี่ยวนะจ๊ะ)

    👨‍🚀 Method overriding

    ในวีดีโอจะไม่ได้พูดเรื่องนี้นะครับ เพราะมันต้องเข้าใจเรื่อง inheritance เสียก่อน แต่ขออธิบายไว้คร่าวๆว่า มันคือการเขียนการทำงานของ method ใหม่จาก class ลูกนั่นเอง โดยคลาสแม่จะต้องประกาศว่า method นั้นเป็น virtual ด้วย

    2019-11

    ✏️ ประวัติการอัพเดท

    สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

    2020-03

    ✏️ ประวัติการอัพเดท

    สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

    9.การตัดสินใจด้วย Switch statements

    💬 หลังจากที่เราได้เห็นการตัดสินใจของคอมพิวเตอร์ผ่านคำสั่ง IF statements กันไปบ้างละ ในรอบนี้เราลองมาดูการตัดสินใจแบบง่ายๆด้วยคำสั่งที่เรียกว่า Switch statements กันดูบ้างนะครับ

    🎯 สรุปสั้นๆ

    16.ลดงานซ้ำๆด้วย Method

    💬 เคยรู้สึกไหมว่าในโค้ดของเราบางทีก็มีงานที่เขียนซ้ำๆกันออกมาให้เจอบ่อยๆ (ซ้ำในที่นี้ไม่ใช่ในแบบของ loop นะ) ซึ่งมันทำให้เราต้องไปคอยนั่งก๊อปปี้มาวางจุดนั้นจุดนู้นตลอดเวลา แล้วยิ่งเราเอาไปวางไว้เยอะ ถ้าเราต้องแก้ไขมันเราก็ต้องไล่ไปตามแก้ทุกจุดด้วยอะดิ จากปัญหาที่ว่ามาในรอบนี้เราจะลองมารู้จักกับสิ่งที่เรียกว่า Method ซึ่งจะมาช่วยคลี่คลายปัญหาที่ว่ามานี้ครับ

    🎯 สรุปสั้นๆ

    17.มารู้จักกับ Class & Field กัน

    💬 เคยหงุดหงิดกันไหมเวลาเขียนโปรแกรมแล้วต้องมาคอยจัดการกับตัวแปรเยอะๆ เช่น ข้อมูลของนักเรียน 1 คนต้องใช้ตัวแปร 4 ตัวเพราะมันมี ชื่อ, นามสกุล, อายุ, รหัสนักเรียน แล้วถ้าเราต้องเก็บข้อมูลนักเรียน 10 คนล่ะ? นั่นแสดงว่าเราต้องมีตัวแปรอย่างน้อย 40 ตัวใช่ไหม? แต่ลองคิดดูนะว่ามันจะปวดกบาลขนาดไหนถ้าต้องจัดการกับนักเรียน 100 คน!! เอาละไม่ต้องทนปวดกบาลอีกต่อไปเมื่อเรารู้จักกับสิ่งที่เรียกว่า Class

    🎯 สรุปสั้นๆ

    Communication Patterns

    🤔 แอพไม้จิ้มฟันยันยานอวกาศเขาออกแบบยังไง ?

    ในคอร์สนี้เราจะมาดูวิธีการแก้ปัญหาของ Distributed System กัน โดยจะเริ่มอธิบายตั้งแต่ การออกแบบซอฟต์แวร์แบบเด็กอนุบาล ยัน Large Scale Enterprise กัน ซึ่งในบทความนี้จะขอไล่ลำดับความวุ่นวายไปเรื่อยๆ และใช้ร้านอาหารที่ทุกคนเข้าใจได้ในชีวิตประจำวันเป็นตัวอธิบายนะขอรับ

    🍚 ร้านอาหารตามสั่ง

    เป็นร้านที่ลูกค้าเดินเข้าไปสั่งอาหารกับ "พ่อครัว" แล้วลูกค้าก็ยืนรอให้เขาทำอาหารจนเสร็จตามที่เราเห็นได้ทั่วไป

    ถ้าเปรียบกับโลกของซอฟต์แวร์แล้วก็คือ แอพที่เด็กหัดเขียนโปรแกรมใหม่ๆชอบทำกัน และทุกอย่างของระบบนี้ต้องพึ่ง

    Structural Patterns

    ตัวช่วยจัดการโครงสร้างของโค้ด

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

    เมื่อพูดถึงการออกแบบว่าเราควรจะมีคลาสอะไรบ้าง? แต่ละคลาสควรมีโครงสร้างยังไงถึงจะช่วยให้เราทำงานได้เร็วขึ้น ไม่ซับซ้อนจนเกินไป บลาๆ ดชแมวน้ำ แนะนำให้ศึกษา Design Patterns ในกลุ่มของ Structural Patterns ด้านล่างนี้ฮั๊ฟ

    int i = 123;      // a value type
    object o = i;     // boxing
    int j = (int)o;   // unboxing
    public enum DaysOfWeek
    {
        Sunday = 0,
        Monday = 1,
        Tuesday = 2,
        Wednesday = 3,
        Thursday = 4,
        Friday = 5,
        Saturday = 6,
    }
    int day = (int)DaysOfWeek.Sunday;
    DaysOfWeek today = (DaysOfWeek)3;
    var today = Enum.Parse<DayOfWeek>("Sunday");
    interface ILogger
    {
        void Log(LogLevel level, string message);
        void Log(Exception ex) => Log(LogLevel.Error, ex.ToString());
    }
    public class Student
    {
       public int Name;
       public Student()
       {
          Name = "Unknow";
       }
    }
    int a = 123;
    string b = "saladpuk";
    👶 ความรู้พื้นฐานในการทำเว็บ
    🗃️ บทสรุปฐานข้อมูล
    Normalization
    อยากทำเว็บมันต้องรู้อะไรบ้างหว่า?
    🗃️ บทสรุปฐานข้อมูล
    คิดแบบตรรกะจำแบบโปรแกรมเมอร์
    เวลาทำงานเหตุผลมาก่อนเสมอ
    บังคับคลีนโค้ดทำไมฟระ ?
    บังคับทำเทสทำไมฟระ ?
    Requirement Driven Design
    🤠 Cloud Playground
    การป้องกันความลับหลุดตอนจบ
    🤠 Cloud Playground
    การป้องกันความลับหลุดตอนที่ 3
    🤠 Cloud Playground
    การป้องกันความลับหลุดตอนที่ 2
    🤠 Cloud Playground
    การป้องกันความลับหลุดตอนที่ 1
    🤠 Cloud Playground
    ลองสร้างโปรเจคเกมตัวแรกกัน
    30/11/2019
    • ปิดจบบทความที่สนุกที่สุดที่เคยเขียนมา 👑 OOP + Power of Design

    28/11/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 👑 OOP + Power of Design

    27/11/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 📝 ลองเขียน OOP ดูดิ๊

    26/11/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 🏆 Inheritance & Polymorphism

    24/11/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 💖 Polymorphism

    22/11/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 💖 Inheritance

    21/11/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 🏆 Abstraction + Encapsulation

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 💖 Encapsulation

    20/11/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 💖 Abstraction

    • เขียนคอร์ส 👶 Object-Oriented Programming

    19/11/2019

    • อัพเดท 👶 Azure Service Fabric เรื่อง สร้าง Service Fabric กัน

    17/11/2019

    • เขียนคอร์ส 👶 Azure Service Fabric

    • อัพเดท 👶 Azure DevOps เรื่อง เล่น Kanban Board

    06/11/2019

    • เพิ่ม เกร็ดความรู้ เรื่อง 👨‍💻 ที่สุดแห่งการเป็นโปรแกรมเมอร์

    05/11/2019

    • เขียนบทความ 👶 Git พื้นฐาน

    04/11/2019

    • อัพเดท 👶 Azure DevOps เรื่อง ลองทำ Continuous Delivery (CD)

    03/11/2019

    • อัพเดท 👶 Azure DevOps เรื่อง ลองทำ Continuous Integration (CI)

    02/11/2019

    • อัพเดท 👶 Azure DevOps เรื่อง เล่นกับ Repository

    • อัพเดท 👶 Azure DevOps เรื่อง เล่น Azure DevOps กัน

    01/11/2019

    • เขียนคอร์ส 👶 Azure DevOps เกริ่นว่ามันทำอะไรได้เบื้องต้น

    Facebook Blog: Mr.Saladpuk

    ช่วงนี้งานของ ดช.แมวน้ำ เยอะม๊วก เลยไม่ค่อยได้อัพเดทบทความเท่าไหร่นะจ๊ะ แต่จะค่อยๆทยอยลงเรื่อยๆเมื่อมีโอกาศ ส่วนใครที่ด่วนอยากได้ความรู้ตรงไหนก็ทัก Facebook ส่วนตัวมาได้นะกั๊ฟ

    28/03/2020

    • เพิ่มบทความ Security Principles - แนวคิดในการรักษาความปลอดภัยของระบบมันมีไยบ้าง ?

    23/03/2020

    • เพิ่มเกร็ดความรู้ กฏเหล็กในการทำงาน - อย่าทำทุกอย่างให้ Perfect ตั้งแต่ครั้งแรก

    21/03/2020

    • เพิ่มบทความ 🎎 Prototype Pattern - แนวคิดในการก๊อปปี้ object แบบง่ายๆ

    19/03/2020

    • เพิ่มเกร็ดความรู้เยียวยาใจ หากเหนื่อยนักขอจงหยุดพักเสียก่อน

    • เพิ่มเนื้อหา ข้อเสียของการทำ Delete Flag ในฐานข้อมูล - ขอบคุณเพื่อนๆที่แนะนำประเด็นที่ตกไปของบทความนี้นะขอรับ

    18/03/2020

    • เพิ่มบทความ การลบข้อมูล - ใครที่มีปัญหากล้าๆกลัวในการลบข้อมูลอยู่ก็ลองศึกษาได้จากตรงนี้เด้อ

    16/03/2020

    • เพิ่มบทความ เก็บรูปในฐานข้อมูล - ใครที่เก็บรูปไว้ในฐานข้อมูลอยู่ มาดูกันว่าบาปเหล่านั้นจะแก้ยังไง

    14/03/2020

    • เพิ่มเกร็ดความรู้เรื่อง ดราม่ากับ PHP - ใครดราไรไว้มาจบกันที่นี่ 🤣

    13/03/2020

    • เพิ่มบทความ Database indexing - ฐานข้อมูลช้า อยากแก้แบบง่ายๆ ไม่ง้อโปรแกรมเมอร์ทำไง ?

    12/03/2020

    • เพิ่มบทความ Git branching strategy - การทำงานบน Git แบบมืออาชีพเขาทำกันยังไงน๊า ?

    05/03/2020

    • เพิ่มคอร์ส 👶 App Service Plan - อยากได้เซิฟเวอร์ที่รองรับคนเป็นล้านๆคนเหรอ? จิ้มโลด

    04/03/2020

    • เพิ่มเกร็ดความรู้เรื่อง อยากเรียนแต่ไม่มีตังเหรอ ?

    03/03/2020

    • เพิ่มคอร์ส 👶 Azure App Services - งานซอฟต์แวร์พื้นฐาน 80% ส่วนใหญ่อยู่กับเจ้านี่แหละ เรียนมันไปเถอะแล้วชีวิตจะสบายขึ้น

    • เพิ่มเกร็ดความรู้เรื่อง แค่ต่างมุมมองทองก็กลายเป็นขี้ได้

    Facebook Blog: Mr.Saladpuk
    static void Saladpuk()
    {
        Console.WriteLine("Hello world");
    }
    delegate void YOUR_DELEGATE_NAME();
    YOUR_DELEGATE_NAME del1 = Saladpuk;
    del1();
    class Program
    {
        delegate void YOUR_DELEGATE_NAME();
    
        static void Main(string[] args)
        {
            YOUR_DELEGATE_NAME del1 = Saladpuk;
            del1();
        }
    
        static void Saladpuk()
        {
            Console.WriteLine("Hello world");
        }
    }
    👨‍🚀 การเขียน Switch แบบมาตรฐาน

    เกร็ดความรู้ 1 ในการเขียน switch นั้นเราสามารถใส่วงเล็บ { } ลงไปใน case หรือ default ได้นะจ๊ะ เพื่อเป็นการบอกว่า scope ของการทำงานอยู่ที่ไหน ตามตัวอย่าง code ด้านล่าง

    เกร็ดความรู้ 2 เราสามารถรวม case ที่ทำงานเหมือนกันเอาไว้ด้วยกันได้ และ รวมถึงการรวม case default ด้วยเช่นกัน ตาม code ด้านล่าง

    👨‍🚀 การเขียน switch โดยใช้ Type pattern

    ในภาษา C# รุ่นใหม่ๆจะรองรับการใช้สิ่งที่เรียกว่า Type pattern แล้ว โดยเราสามารถเอาชนิดข้อมูลมาใช้เป็นเงื่อนไขได้

    👨‍🚀 การเขียน switch โดยใช้ When clause

    👨‍🚀 Method คือ

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

    👨‍🚀 Method แบบไม่มี parameter

    👨‍🚀 Method แบบมี parameter

    แบบมี parameter ตัวเดียว

    แบบมี parameter หลายตัว (ใช่ comma คั่น)

    👨‍🚀 Method แบบมีการส่งข้อมูลกลับ (return type)

    แบบมี return type แต่ไม่มี parameter

    แบบมี return type และมี parameter

    👨‍🚀 out keyword

    เป็นการบอกว่า parameter ที่ส่งเข้ามานั้นจะให้ method เป็นคนกำหนดค่าให้มัน ดังนั้นเมื่อ method นั้นๆทำงานเสร็จ ตัวแปรที่ส่งเข้าไปให้ด้วย out keyword นั้นก็จะถูกกำหนดค่ามาให้เสร็จเรียบร้อยเลย

    นั่นหมายความว่า method ที่รับ parameter เป็น out keyword จะต้องมีการกำหนดค่าให้กับ parameter นั้นๆด้วย ซึ่งถ้าไม่ทำการกำหนดค่าให้เราจะไม่สามารถ compile ไฟล์นั้นได้ครับ

    👨‍🚀 ref keyword

    เป็นการบอกว่า parameter ที่ส่งเข้ามานั้น ถ้า method มีการแก้ไขค่าให้เป็นอะไร ตัวแปรจริงๆที่ถูกส่งเข้ามาก็จะถูกแก้ไขตามไปด้วย ซึ่งส่วนใหญ่เราจะใช้กับ value type นั่นเอง

    นั่นหมายความว่า method ที่รับ parameter เป็น ref keyword ไม่จำเป็นต้องมีการกำหนดค่าให้กับ parameter นั้นๆ เพียงแต่ถ้าเราเปลี่ยนค่ามันภายใน method มันก็จะถูกเปลี่ยนตามไปด้วยนั่นเอง

    👨‍🚀 คลาสคือ

    การสร้างชนิดข้อมูลแบบใหม่ที่เราสามารถกำหนดได้เองว่ามันจะประกอบไปด้วยอะไรบ้าง เช่นข้อมูลของนักเรียน 1 คนที่ประกอบไปด้วย ชื่อ, นามสกุล, อายุ, รหัสนักเรียน แทนที่เราจะเขียนโค้ดแบบด้านล่าง

    เราก็สามารถมาสร้างเป็นชนิดข้อมูลแบบใหม่ที่เราเรียกมันว่า Student แทน

    นิยามจริงๆของ Class คือ ต้นแบบ หรือ พิมพ์เขียว แต่คิดว่าอธิบายแบบนี้ละก็หลายๆคนคงกดเปลี่ยนไปดูการ์ตูนแล้วล่ะเลยขออธิบายแบบนี้ไปก่อนละกันนะ เดี๋ยวค่อยเข้าใจมันอีกทีตอนที่อธิบายเรื่อง OOP หรือที่เราเรียกเต็มๆกันว่า Object Oriented Programming

    Class เป็น Reference type เด้อ

    👨‍🚀 การเรียกใช้งานคลาส

    การใช้งาน class เราจะต้องทำการสร้าง object ขึ้นมาก่อนผ่านคำสั่ง new ตามตัวอย่างด้านล่าง

    เมื่อเราต้องการใช้งานตัวแปรต่างๆของคลาสนั้นๆเราก็จะทำการกดปุ่ม (จุด) . เพื่อบอกว่าจะทำงานกับตัวแปรไหนของคลาสนั้นๆ ตามตัวอย่างด้านล่าง

    👨‍🚀 Access Modifiers

    คือตัวที่ใช้บอกว่าตัวแปรในคลาสนั้นๆ เปิดสิทธิ์ให้คนภายนอกเรียกใช้แบบไหนได้บ้าง ซึ่งมีทั้งหมด 6 ชนิด

    No

    Access modifier

    ความหมาย

    1

    public

    เปิดให้ทุกคนเข้าใช้งานได้

    2

    private

    ให้เฉพาะคนในคลาสมันเองใช้งานได้เท่านั้น

    3

    protected

    ให้เฉพาะคลาสมันเอง และ คลาสลูกเท่านั้น

    4

    ถ้าไม่เตรียมไปสอบ ก็ไม่ต้องไปนั่งจำมันหรอก ใช้ๆไปเดี๋ยวก็จำได้ทั้งหมดเอง แต่สิ่งที่ควรจะจำเป็นพื้นฐานเลยคือ public กับ private เท่านั้นก่อนนะจ๊ะ

    พ่อครัว
    เท่านั้น เพราะตั้งแต่การจดเมนู เสริฟน้ำ เดินไปเข้าครัว ยกอาหารมาให้ คิดเงิน เก็บโต๊ะ บลาๆ และเนื่องจากมีพ่อครัวคนเดียว ดังนั้นทุกอย่างเลยเป็น
    Synchronous ทั้งระบบเลย
    เป็นทุกอย่างให้เธอแหล๊วว ~ 🎵

    Synchronous คือการไปทำอะไรซักอย่าง แล้วต้องรอจนกว่าจะทำสิ่งนั้นๆเสร็จ โดยที่ไปทำอย่างอื่นต่อไม่ได้ เช่น พ่อครัวไปทำข้าวในครัว ระหว่างที่ทำข้าวอยู่ เขาก็จะแยกร่างมาเดินเสริฟน้ำไม่ได้นั่นเอง

    🤕 จุดอ่อนของระบบ

    เนื่องจากทั้งระบบต้องพึ่ง พ่อครัว แต่พ่อครัวดันมีคนเดียว ดังนั้นทุกอย่างเลยต้องหยุดรอพ่อครัว เลยทำให้ คอขวด ที่ทำให้ระบบนี้พังก็คือพ่อครัวนั่นเอง

    ตัวอย่าง สมมุติว่าพ่อครัวทำอาหาร 1 จาน ต้องใช้เวลาทำราวๆ 10 นาที (รับออเดอร์, เตรียมกับข้าว, ทำกับข้าว, เช็คบิล, เก็บโต๊ะ บลาๆ) นั่นหมายความว่า สุดๆแล้วร้านนี้จะรับทำอาหารได้สูงสุด 6 จาน ต่อ 1 ชั่วโมง นั่นเอง

    🩹 การรักษา

    จากปัญหาด้านบนงั้น ก็เพิ่มพ่อครัวดิ!! . . . เพื่อให้สามารถทำหลายๆอย่างพร้อมๆกันได้หรือที่เราพูดติดปากกันว่าแบบ Parallel นั่นเอง ซึ่งฟังแล้วก็ดูเหมือนจะง่ายนะ แต่การแก้ปัญหาจริงๆนั้น ถ้าเพิ่มพ่อครัวเพียงอย่างเดียว มันจะนำปัญหาแบบอื่นตามมา ซึ่งในโลกของซอฟต์แวร์ปัญพวกนี้คือสิ่งที่เราจะเจอในการทำ Distributed System นั่นเอง ดังนั้นเราลองดูวิวัฒนาการของร้านหารในรูปแบบถัดไปกันดีกั่ว

    🍛 ร้านอาหารตามสั่ง V.2

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

    การเพิ่มกำลังในโลกของซอฟต์แวร์มีหลากหลายวิธี แต่ทุกวิธีนั้นมีหัวใจหลักเดียวกันคือ 💖 การแบ่งง่านออกเป็นส่วนๆ เช่นกัน เพราะจากเดิมที่ระบบทำงานเป็น Synchronous มันจะเริ่มกลายเป็น ระบบ Asynchronous แล้วนั่นเอง ตามรูปด้านล่าง

    Asynchronous คือการทำอะไรซักอย่าง แต่ไม่ต้องรอจนให้มันเสร็จ ก็สามารถไปทำอย่างอื่นต่อได้ เช่น คนรับออเดอร์เดินไปบอกพ่อครัวว่าลูกค้าสั่งอะไรเสร็จ ก็สามารถกลับมารับออเดอร์ต่อได้เลย โดยไม่ต้องรอให้พ่อครัวทำอาหารนั้นๆจนเสร็จนั่นเอง (จำง่ายๆคือมันตรงข้ามกับ synchronous งุยล่ะ)

    🤕 จุดอ่อนของระบบ

    ในโลกของซอฟต์แวร์ถ้าเราแบ่งงานออกเป็นส่วนๆได้ เราก็จะสามารถขยายความสามารถได้โดยการทำ Scaling แบบต่างๆ แต่ปัญหาที่แท้จริงของมันคือ งานแต่ละส่วนจะคุยกันรู้เรื่องได้ยังไง? เช่น ครัวต้มอาหารจะรู้ได้ยังไงว่าเมื่อทำเสร็จต้องส่งต่อไปให้ครัวทอดหรือครับปิ้ง? หรือไม่จำเป็นต้องส่ง? หรือเมื่อทำเครื่องดื่มเสร็จมันจะต้องเอาไปวางคู่กับอาหารจานไหน? เนื่องจากที่เราเปลี่ยนระบบกลายเป็น Asynchronous เลยทำให้งานแต่ละส่วนมันทำโดยไม่ต้องรอกันได้ ดังนั้นปัญหาที่ตามมาคือ การคุยกันของแต่ละเซอร์วิสนั่นเอง (Service Communication Problems) ซึ่งถ้าเราไม่จัดการกับเรื่องนี้ เราจะรู้ได้ไงว่าออเดอร์นั้นๆเสร็จหรือยัง?

    ตัวอย่าง สมมุติว่าลูกค้ากดปุ่มสั่งซื้อสินค้าและจ่ายเงินเสร็จ แต่ระบบเราเป็นแค่คนกลาง เลยต้องส่งบิล ส่งออเดอร์ไปยังร้านค้าต่างๆ แต่เราไม่สามารถตอบลูกค้าได้ว่าสถานะการสั่งซื้อเป็นยังไง แล้วลูกค้าจะ happy กับระบบเราหรือเปล่า?

    🩹 การรักษา

    ปัญหาเกิดจากการแยกส่วนรับผิดชอบกัน ดังนั้นวิธีการแก้ปัญหาคือ ควมคุมการสื่อสาร ระหว่างแต่ละ module นั่นเอง ซึ่งในทางเทคนิคเราเรียกเรื่องพวกนี้ว่า การทำ Messaging หรือการทำ Communication นั่นเอง

    🧭 เนื้อหาทั้งหมดของคอร์สนี้

    จากที่เล่าไปทั้งหมดเราก็จะมาดูรูปแบบต่างๆในการควมคุมการสื่อสาร หรือที่เราเรียกกันว่า Communication Patterns กันดีกั่วขอรับ ตามหัวข้อด้านล่างเบย

    • สั่งอาหารคุยตัวต่อตัว

    • ใบสั่งอาหาร Request & Response

    • ตะโกนเอา Event base

    • หัวหน้าเชฟ Orchestration

    ใจเย็นนะโยมนะ คอร์สนี้กำลังค่อยๆเขียนอยู่ แมวน้ำมีเวลาว่างเมื่อไหร่ก็จะมาอัพเดทเรื่อยๆ แต่ถ้าไม่อยากพลาดบทความใหม่ๆก็เข้าไปกดติดตามได้จากลิงค์นี้เบย Facebook Blog: Mr.Saladpuk กดไลค์ด้วยจะดีมากเลยขอรับ 😍

    😭 ใครช่วยแก้ปัญหานี้ได้บ้าง ?

    ในกุล่มของ Structural Patterns นี้มีพระเอกหลายตัวเลยที่จะมาช่วยแก้ปัญหาเรื่องการออกแบบในระดับโครงสร้างกันเลยทีเดียว ซึ่งเจ้าตัว design แต่ละตัวมันจะเก่งกันคนละเรื่อง ขึ้นอยู่กับว่าปัญหาที่เราเจอเป็นแบบไหน ดังนั้นไปดูว่าแต่ละตัวมันช่วยแก้ปัญหาอะไรเลยละกัน

    🔌 Adapter Pattern

    เปลี่ยนของ 2 อย่างที่ทำงานด้วยกันยากๆ มาทำงานด้วยกันได้ง่าย

    📪 Proxy Pattern

    ควบคุม object ให้ทำงานดั่งใจ

    • Bridge Pattern (ว่างเดี๋ยวกลับมาเขียน)

    • Composite pattern (ว่างเดี๋ยวกลับมาเขียน)

    • Decorator Pattern (ว่างเดี๋ยวกลับมาเขียน)

    • Facade Pattern (ว่างเดี๋ยวกลับมาเขียน)

    • Flyweight pattern (ว่างเดี๋ยวกลับมาเขียน)

    คำเตือน ไม่ควรนำ Design Patterns ไปใช้ โดยไม่ได้ชั่ง ผลดี/ผลเสีย ให้เรียบร้อยก่อน เพราะมันจะทำให้โค้ดเราซับซ้อนโดยไม่จำเป็น แล้วจะแก้ไขปรับปรุงอะไรต่างๆก็จะกลายเป็นเต่า แต่จงศึกษา Design Patterns เพื่อเรียนรู้หลักในการออกแบบ เพื่อจะได้รู้ว่าเมื่อไหร่ไม่ควรใช้ Pattern

    คอร์สนี้กำลังค่อยๆเขียนอยู่ ใครที่ไม่อยากพลาดอัพเดทก็เข้าไปกดติดตามที่ลิงค์ Mr.Saladpuk ได้เลย ส่วนใครที่อยากศึกษา pattern ตัวไหนล่วงหน้าก็ไปอ่านบทความเก่าได้ที่ลิงค์นี้ 🤴 Design Patterns (อ่านแล้วเมากาวไม่รู้ด้วยนะ) + ในคอร์สนี้จะเริ่มอธิบาย Pattern แต่ละตัวจากกลุ่มนี้ก่อนนะครัช

    ช่องทางสนับสนุนค่าอาหารแมวน้ำกั๊ฟ 😘
    OCP
    for( INITIALIZER; CONDITION; ITERATOR )
    {
       // เข้ามาทำงานตรงนี้ถ้าเงื่อนไขยังเป็นจริงอยู่
    }
    for(int round = 0; round < 10; round++, a--)
    {
       // Do something
    }

    คำสั่ง

    ความหมาย

    System.Convert.ToInt32( x );

    แปลง x ให้กลายเป็น int

    System.Convert.ToDouble( x );

    แปลง x ให้กลายเป็น double

    System.Convert.ToString( x );

    แปลง x ให้กลายเป็น string

    https://code.visualstudio.com
    https://dotnet.microsoft.com/download

    26.Namespace

    💬 วันนึงพอเราเขียนโปรแกรมไปเยอะๆมีคลาสต่างๆมากมาย เราจะแบ่งแยกยังไงว่าคลาสพวกนั้นมันทำงานอยู่ในหมวดหมู่ไหนบ้าง เช่นหมวดคำนวณ หมวดตรวจสอบความถูกต้อง หรือคลาสนั้นๆเป็นแค่ model (คืออะไรหว่า?) ซึ่งจากปัญหาที่ว่ามา พระเอกที่จะมาช่วยเราในตอนนี้ก็คือสิ่งที่เรียกว่า Namespace นั่นเอง

    🎯 สรุปสั้นๆ

    👨‍🚀 Namespace คือ

    เป็นแค่วิธีการแบ่งแยกกลุ่มของโค้ดของเรา โดยเราจะใช้แบ่งหมวดการทำงานของโค้ดแต่ละเรื่องว่ามันใช้สำหรับทำอะไร เช่นจากตัวอย่างด้านล่างผมแยกโค้ดออกเป็น 2 กลุ่มคือ 1.ของที่เป็น data model กับ 2.ของที่ใช้ต่อฐานข้อมูล

    Sub namespace เราสามารถสร้าง namespace ย่อยๆลงไปได้เรื่อยๆ โดยใช้เครื่องหมายจุด . นะครับ

    Nested namespace เราสามารถสร้าง namespace ภายใน namespace ได้เหมือนกัน และมันก็คือการทำ sub namespace ธรรมดานั่นแหละ ซึ่งปรกติเขานิยมใช้จุดแทนธรรมดาแทนการสร้าง nested namespace ครับ

    👨‍🚀 using keyword

    เวลาที่เราจะเรียกใช้ namespace อะไร เราจะต้องใช้คำสั่ง using keyword ไว้ด้านบนสุดของไฟล์ เพื่อบอกว่าโค้ดที่เรากำลังเขียนอยู่นี้มันสามารถใช้ namespace อะไรได้บ้าง ซึ่งถ้าผมอยากจะใช้คลาส Teacher จากตัวอย่างด้านบนผมจะต้องเขียนโค้ดไว้ตามตัวอย่างด้านล่างนี้เบย

    👨‍🚀 Aliases

    ในบางครั้งเราไม่อยากนำ namespace เข้ามาใช้ทั้งหมด เราก็สามารถสร้าง aliases ให้กับ namespace ที่จะใช้ได้ด้วยนะ ตามโค้ดด้านล่างเลย ผมใช้ aliases ที่บรรทัดที่ 1 และเรียกใช้งานมันบรรทัดที่ 9

    29.ลงลึกกับ string

    💬 ในรอบนี้เราจะมาลองทำความเข้าใจเจ้า string กันแบบลึกๆบ้างว่ามันเป็นยังไงกันแน่นะ

    🎯 สรุปสั้นๆ

    👨‍🚀 รูปแบบของ string ที่แท้จริง

    ตัว string จริงๆมันจะคล้ายๆกับ array แต่มันจะเป็น array ของ char เช่นคำว่า "Hello" มันก็จะเหมือนกับมันเก็บข้อมูลแยกเป็นทีละตัว H, e, l, l, o ตามลำดับ ดังนั้นเราจะสามารถเรียกดูข้อมูลของ string ในแต่ละตำแหน่งได้ เช่น

    Read-only collection of Char ตัว collection ของ string เราสามารถเข้าถึงตำแหน่งของมันแต่ละตัวได้ แต่เราไม่สามารถเปลี่ยนแปลงค่ามันได้นะจ๊ะ

    IndexOutOfRangeException คือ error เวลาที่เราเรียกข้อมูลใน collection ในตำแหน่งที่มันไม่มีอยู่

    string กับ String มันเหมือนกันนะ เพียงแค่ตัว string (ตัวเล็ก) มันเป็นตัวย่อของ class String นี่แหละ

    👨‍🚀 Immutability

    ตัว string ที่ถูกสร้างขึ้นมาทุกตัวจะมีความเป็น immutable เสมอ หรือแปลง่ายๆว่า มันแก้ไขเปลี่ยนแปลงอะไรอีกไม่ได้ ถ้าสร้างมันขึ้นมาแล้ว (ไปดูบทถัดไปจะเห็นภาพที่ชัดขึ้น)

    👨‍🚀 Regular & Verbatim

    string เวลาใช้งานมันสามารถใช้อักขระพิเศษทำงานด้วยเพื่อให้เกิดการทำงานแบบพิเศษๆได้ เช่น ขึ้นบรรทัดใหม่ด้วย \n หรือเป็นการเว้นยาวๆด้วย \t

    ข้อควรระวังในการใช้อักษรพิเศษ โดยปรกติเราไม่ควรจะใช้อักษรพิเศษในการทำงานร่วมกัน string (แม้ว่ามันจะทำงานได้ก็ตาม) เพราะมันจะทำงานได้เฉพาะแค่กับ OS ใด OS นึงเท่านั้น เช่นการขึ้นบรรทัดใหม่ของ Windows กับ Linux หรือ Mac ก็ไม่เหมือนกันละ (ตัวนึงเป็น LF อีกตัวนึงเป็น CRLF) ซึ่งวิธีที่เหมาะสมในการทำงานจริงๆคือการใช้คลาส Environment เข้ามาช่วยนั่นเอง

    👨‍🚀 Environment class

    ตัวช่วยในการเลือกของที่เหมาะสมกับสภาพที่โปรแกรมกำลังทำงานอยู่ เช่นถ้าโปรแกรมทำงานอยู่บน Windows มันก็จะเลือกการขึ้นบรรทัดใหม่ในรูปแบบของ Windows แต่ถ้าโปรแกรมทำงานอยู่บนเครื่อง Mac มันก็จะขึ้นบรรทัดใหม่ในรูปแบบเครื่อง Mac ตามตัวอย่างโค้ดด้านล่าง

    👨‍🚀 Format strings

    เราสามารถจัดการรูปแบบการแสดงผลของ string ได้หลายรูปแบบ

    Placeholder - การเว้นพื้นที่เพื่อระบุว่าพื้นที่นั้นๆจะใช้ค่าอะไรมาใส่

    Interpolation - เหมือนกับ placeholder เลย เพียงแค่กำหนดไปเลยว่าจุดนั้นๆใช้ตัวแปรอะไร

    String interpolation ใช้ได้กับ C# version 6 ขึ้นไปน่าจ๊า

    👨‍🚀 Method ต่างๆของ string

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

    Substring - ตัด string ออกจากกัน โดยต้องระบุตำแหน่งที่จะตัดและจำนวนตัวอักษร

    IndexOf - เป็นการค้นหาว่าคำที่ระบุอยู่ในตำแหน่งที่เท่าไหร่

    Replace - แก้ไขในข้อความที่กำหนดให้เป็นค่าใหม่

    👨‍🚀 การตรวจสอบ null กับ string ว่าง

    การตรวจสอบว่า string มีค่าเป็น null หรือเป็นค่าว่างหรือเปล่า ทาง Microsoft นิยมตรวจสอบโดยใช้ method ที่ชื่อว่า string.IsNullOrWhiteSpace()

    7.การเปรียบเทียบค่า

    อาวล๊าาา หลังจากที่เริ่มชินกับการสั่งให้คอมมันจำข้อมูลต่างๆ หรือการแปลงข้อมูลจากชนิดหนึ่งไปยังอีกชนิดหนึ่งละ คราวนี้สิ่งที่คนเขียนโค้ดจะต้องเจอกันคือ การเปรียบเทียบ ระหว่างๆของต่างๆ เช่น อายุเกิน 18 ปีหรือเปล่า? หรือต้องเทียบว่าค่าจากตัวแปร A มากกว่าตัวแปร B หรือเปล่าอะไรทำนองนี้ เราจะต้องเขียนยังไงกันน๊าาา ปะไปดูวีดีโอกันเร๊ยยยย (ครูภาษาไทยมาเจอ ดช.แมวน้ำ ในตอนนี้น่าจะปวดกบาลน่าดูเรยเน๊อะ)

    🎯 สรุปสั้นๆ

    👨‍🚀 คำสั่งในการเปรียบเทียบข้อมูล

    เวลาเราจะเปรียบเทียบอะไรกันก็ตามเราจะใช้คำสั่งในการเปรียบเทียบข้อมูล ซึ่งผลจากการเปรียบเทียบนั้น เราจะได้กลับมาเป็นข้อมูลชนิด bool นะจุ๊

    👨‍🚀 คำสั่งในการเชื่อมการเปรียบเทียบข้อมูล

    เวลาที่เราเปรียบเทียบข้อมูลหลายๆเงื่อนไข เราก็จะใช้ตัวเชื่อมต่างๆ เช่นเงื่อนไขว่า "อายุมากกว่า 18 ปี และ ต้องเป็นคนไทยด้วย" จะเห็นว่าตัวเชื่อในตัวอย่างนี้คือคำว่า และ นั่นเอง ซึ่งในภาษา C# มาตัวเชื่อมตามนี้

    วิธีเชื่อมเงื่อนไข โดยปรกติการเขียนเราจะไม่นิยมใช้ตัวเชื่อมแบบโดดๆ เช่น a & b นะครับ แต่เราจะใช้คำสั่งซ้ำสองรอบกลายเป็น a && b เพราะมันเร็วกว่า

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

    (ถ้าอ่านแล้วไม่ไม่เข้าใจไม่เป็นไรใช้ๆไปเต๊อะแบบไหนก็ได้)

    จำง่ายๆ 1. ถ้าเราใช้ & ให้ดูว่าภายในเงื่อนไขมี false ไหม ถ้ามีแสดงว่าผลลัพท์คือ false ครับ 2. ถ้าเราใช้ | ให้ดูว่าภายในเงื่อนไขมี true ไหม ถ้ามีแสดงว่าผลลัพท์คือ true ครับ

    20.มารู้จักกับ Property กัน

    💬 ถ้าเราอยากให้ตัวแปรที่เป็น private ถูกภายนอกกำหนดค่าหรืออ่านค่าได้ โดยปรกติเราก็ต้องสร้าง method ขึ้นมา 2 ตัว สำหรับเขียนและสำหรับอ่านชิมิ ดังนั้นถ้าเรามีตัวแปรแบบนั้น 20 ตัว นั่นก็หมายความว่าเราก็จะมี 40 method อะจิ!! แบบนี้เราคงไม่ต้องทำอะไรกันพอดีเพราะคลาสเราจะรกไปด้วย method นั่นเอง ดังนั้นในรอบนี้เราจะมารู้จักกับ Property ที่จะเป็นพระเอกมาช่วยเราในเรื่องนี้เองงับ

    🎯 สรุปสั้นๆ

    👨‍🚀 Property คือ

    Method พิเศษตัวนึงที่ช่วยให้เราเข้าถึงตัวแปรได้ง่ายๆ ผ่าน accessor ที่ชื่อว่า get กับ set โดยเราสามารถเลือกได้ว่า property ที่เราสร้างขึ้นมาจะทำงานกับตัวแปรไหนได้ ตามโค้ดด้านล่าง

    Get คือตัวช่วยให้เราสามารถเข้าไปเรียกดูข้อมูลได้

    Set คือตัวช่วยให้เราสามารถเข้าไปเขียนข้อมูลได้

    value keyword คำสั่ง value ที่อยู่ใน set นั่นหมายถึงค่าที่ผู้ใช้ส่งมากำหนดให้ตอนเรียกใช้ property Name

    👨‍🚀 Auto-Implemented Property

    คือ property ที่เราไม่ต้องไปกำหนดว่ามันจะทำงานกับตัวแปรตัวไหนเลย ซึ่ง C# จะเป็นคนจัดการให้ เรามีหน้าที่แค่กำหนด get set ของมันก็พอ ตามโค้ดด้านล่างเลย

    เกร็ดความรู้ โดยปรกติ Auto-Implemented Property ตอนที่มัน compile มันจะไปแอบสร้างตัวแปรชื่อมั่วๆขึ้นมา เพื่อให้ Property ที่ชื่อว่า Name สามารถทำงานกับตัวแปรที่ถูกแอบสร้างขึ้นมาผ่าน get และ set ได้นั่นเอง

    👨‍🚀 Property คัดกรองข้อมูลให้กับตัวแปร

    เราสามารถเขียนโค้ดเพื่อจัดการกับเวลาที่มีคน แก้ไขข้อมูล หรือ เรียกดูข้อมูล ผ่าน property ได้ตามโค้ดด้านล่าง

    ข้อความระวัง ในตัวอย่างบรรทัดที่ 12 จะเห็นว่าผมเขียนโค้ดต่อ string ด้วยคำสั่ง + (concatenation) ซึ่งโค้ดก็ทำงานได้อยู่นะ แต่เอาจริงๆเราไม่ควรเขียนแบบนั้นเพราะมันจะทำให้ประสิทธิภาพของแอพตกลง ซึ่งเราควรเขียนยังไงผมจะขอยกไปอธิบายในเรื่องของ string ในบทถัดๆไปนะครับ

    👨‍🚀 Access Modifier กับ Proper

    เราสามารถกำหนด access modifier ให้กับพวก accessors ได้นะครับ เช่นผมอยากให้ ทุกคนเรียกดูตัวแปร Name ได้ แต่ให้คลาสมันเองเท่านั้นที่แก้ไขได้ ผมก็จะได้โค้ดตามรูปเบย

    👨‍🚀 Accessors ของ Property

    พวก accessors จริงๆจะมีทั้ง 2 ตัว หรือจะมีแค่ตัวใดตัวนึงก็ได้นะ ตามโค้ดด้านล่างเลยงับ

    ข้อควรระวัง โค้ดด้านบนไม่ค่อยเหมาะสมที่จะเขียนแบบนั้นนะครับ แต่ผมว่ามันเห็นแล้วเข้าใจง่ายดี ฮา

    Amazon เสา 2 ต้น

    โจทย์สอบสัมภาษณ์เข้า Amazon

    🥳 โจทย์

    โจทย์ข้อนี้เห็นว่า Amazon ใช้สอบสัมภาษณ์ โดยเข้าให้โจทย์เรามาประมาณนี้

    Interview Question A cable of 80 meters (m) is hanging from the top of two poles that are both 50 m from the ground. What is the distance between the two poles, to one decimal place, if the center of the cable is 10 m above the ground?

    ซึ่งแปลเป็นไทยง่ายๆได้ว่า มีเชือกยาว 80 เมตรแขวนไว้บนยอดเสา 2 ต้นที่สูงจากพื้น 50 เมตร ถ้ากึ่งกลางของเชือกที่ผูกไว้หย่อนลงมาห่างจากพื้น 10 เมตร ระยะห่างระหว่างเสาทั้งสองต้นคือเท่าไหร่?

    คำถามนี้เด็ก ป.3 ก็สามารถตอบได้ โดยไม่ต้องใช้สูตรอะไรทั้งสิ้น

    โจทย์ที่เป็น Interview Question ที่ดีส่วนใหญ่จะมองปุ๊ปแล้วสามารถตอบได้ภายในไม่กี่วินาที 😉

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

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

    🤠 วิธีคิด

    อย่าที่บอกไปว่าโจทย์ Interview ส่วนใหญ่จะสามารถตอบได้ภายในเวลาไม่กี่วินาที เพราะโจทย์เหล่านั้นเป็นการใช้วัดไหวพริบในการใช้ตรรกะพื้นฐานนั่นเอง ส่วนใครที่ตอบไม่ได้ภายในไม่กี่วิก็ไม่ต้องน้อยใจไปเพราะแมวน้ำก็ทำไม่ได้ 🤣

    เริ่มต้นเราจะรู้ว่ามี เสาสูง 50 เมตร เชือกยาว 80 เมตร และ เชือกอยู่ห่างจากพื้น 10 เมตร ตามรูปด้านล่าง

    ถ้าต้องการให้เชือกอยู่ห่างจากพื้น 10 เมตร เราก็ ต้องใช้เชือกยาว 40 เมตร ซึ่งเราก็จะ เหลือเชือกอีก 40 เมตร ตามรูปด้านล่าง

    ถัดมาเราก็ต้องเอาเชือกที่เหลือ 40 เมตร ย้อนกลับไปแขวนไว้กับเสาอีกต้น ตามรูปด้านล่าง

    ซึ่งหมายความว่า แค่ปล่อยเชือกลงแล้วดึงกลับขึ้นก็ใช้หมด 80 เมตรแล้ว ซึ่งการที่จะทำแบบนั้นได้หมายความว่า เสาทั้งสองต้นต้องอยู่ติดกันเป๊ะๆเลย ดังนั้นระยะห่างของมันคือ 0 นั่นเองครัช (จุดงอของเชือกก็ต้องพับแบบหักศอกด้วย 🤣) ตามรูปด้านล่าง

    สรุปคำตอบคือ 0 นั่นเองกั๊ฟ

    เรื่องการหารเลขจะถูกสอนตอน ป.3 ส่วนเรื่องบวกลบเลขจะถูกสอนตอน ป.1 ดังนั้นแค่ใช้ความรู้ระดับประถมต้นก็สามารถแก้โจทย์ข้อนี้ได้แล้วนั่นเอง

    🤔 ใช้พีทาโกรัสได้มะ

    แมวน้ำเชื่อว่ามีหลายคนที่เห็นโจทย์ข้อนี้แล้วก็นึกถึงทฤษฎีและสูตรต่างๆลอยขึ้นมาบนหัวชิมิ แต่ถ้าใครได้ลองทำจริงๆก็จะพบว่า มันหาคำตอบไม่ด๊ายยยยยยยยยย 😱

    สนใจก็ลองไปดูของพ่อใหญ่นี่ละกัน ซึ่ง Channel นี้มีคำถามสนุกๆเยอะเบย + แมวน้ำจะไม่เอาคำถามจากพ่อใหญ่นี่มาเล่นละ เพราะกันคนแอบไปดูเฉลย 🤣

    ในวีดีโอเขาเพิ่มคำถามเข้าไปอีกข้อให้ เชือกอยู่สูงจากพื้นเป็น 20 เมตรด้วยนะ ใครสนใจก็ตามไปดูต่อได้

    🎯 ข้อคิดที่ได้

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

    CP หมวก 5 ใบ

    โจทย์สอบสัมภาษณ์ผู้บริหาร CP

    🥳 โจทย์

    มีคนสวมหมวกอยู่ในห้อง 5 คนยืนหันหน้าไปทางทิศเดียวกัน โดยห้ามพูด ห้ามหันหน้าไปทางอื่น

    • ไม่มีใครรู้ว่าตัวเองใส่หมวกสีอะไร แต่จะเห็นหมวกทุกคนที่อยู่ด้านหน้า

    • คนแรกเพียงคนเดียวที่อยู่ในห้องมืดทำให้ไม่มีใครรู้ว่าเขาใส่หมวกสีอะไร

    • ทุกคนในห้องรู้ว่ามีหมวกสีแดง 2 ใบ และสีดำ 3 ใบ

    ใครจะเป็นคนแรกที่ตอบว่าตัวเองใส่หมวกสีอะไร และต้องถูก 100%

    🤠 วิธีคิด

    คำถามชุดนี้เป็นการใช้ information เพื่อประกอบการตัดสินใจอย่างมีเหตุผล ซึ่งเป็นหลักคิดพื้นฐานของ dev ทุกคนควรจะต้องมี สนใจไปอ่านต่อได้จากลิงค์นี้ (เข้าไปอ่านการ์ตูนอันสุดท้ายก็คุ้มละผมว่า 🤣)

    ทั้งหมดเริ่มจากการดูว่า ใครมีข้อมูลอะไร? โดยเมื่อวิเคราะห์แล้วก็จะได้คำตอบแบบง่ายๆออกมาว่า A กับ B มองไม่เห็นใครเลย ดังนั้นคนนี้ไม่มีทางที่จะตอบถูกทุกครั้งได้แน่นอน เลยทำให้ 2 ตัวเลือกนี้ถูกตัดทิ้งไปทันที

    ถัดมา C มองเห็นแค่ B เพียงคนเดียว ซึ่งข้อมูลที่เป็นประโยชน์มากสุดคือกรณีที่ B ใส่หมวกสีแดง ก็จะทำให้ C รู้ว่าเหลือหมวกสีแดงอีก เพียง 1 ใบเท่านั้น และมีโอกาสที่เขาจะตอบถูกอยู่ 33.33% (หนึ่งในสาม) ดังนั้นเลยไม่ใช่ทางเลือกที่ดีนักที่จะตะโกนคำตอบออกไป ดังนั้นตัวเลือกนี้ก็ตัดทิ้งได้เลยเช่นกัน

    และแม้ว่าจะมีโอกาสตอบถูกถึง 33% ก็ตาม แต่ถ้าเราลองคิดดูว่าโอกาสที่ B จะใส่หมวกสีแดงมีกี่เปอร์เซ็นก็จะพบว่า มีโอกาสแค่ 4 ใน 10 หรือเพียง 40% เท่านั้นเอง ตามตารางด้านล่าง ดังนั้นโอกาสที่จะเกิดเหตุการณ์แบบนั้นได้และตอบถูกด้วย ก็จะเหลือแค่ 13.2% เท่านั้น

    พอมาวิเคราะห์ตาของ D ดูบ้างก็จะเห็นว่า กรณีที่ดีที่สุดก็คือ B กับ C ใส่หมวกสีแดง ก็จะทำให้ D รู้ว่าหมวกสีแดงไม่มีเหลือแล้ว ทำให้เขารู้ได้ทันทีว่าตัวเองใส่หมวกสีดำ และแน่นอนว่าถ้าเป็นกรณีนี้ E ก็จะรู้คำตอบเช่นกัน แต่การที่จะเกิดเหตุการณ์แบบนั้นขึ้นได้นั้นโอกาสของมันคือ 10% ดังนั้นก็ตัด D ออกได้เลยเช่นกัน

    สุดท้ายคือ E ที่มีโอกาสจะตอบได้มากที่สุด แต่ก็มีแค่เพียง 30% เท่านั้นที่เขาจะเห็นคนข้างหน้าใส่หมวกสีแดง 2 ใบ (A อยู่ในห้องมืดทำให้มองไม่เห็น) ดังนั้น E ก็ไม่ใช่คนที่สามารถตอบได้ 100% นั่นเอง

    ดังนั้นคำตอบในกรณีที่ ไม่ได้กำหนดชัดเจนว่าแต่ละคนใส่หมวกสีอะไร ก็จะหาคนที่ตอบถูก 100% ไม่ได้ นั่นเอง

    🤔 ถ้าหมวกถูกกำหนดไว้แล้วล่ะ?

    ในกรณีที่หมวกถูกกำหนดไว้ตายตัวตั้งแต่ต้นแล้ว และทั้ง 5 คนไม่รู้เช่นเคย ตามรูปด้านล่าง

    3 ตัวเลือกแรก A~C ก็ยังถูกตัดออกเช่นเคย เพราะ

    • A และ B มืด 8 ด้านมองอะไรไม่เห็นดังนั้นโอกาสถูกมีแค่ 50%

    • C รู้ว่ายังเหลือหมวกสีดำ 2 ใบ และแดง 2 ใบ ดังนั้นโอกาสตอบถูกก็มีแค่ 50%

    ถัดมา D จะรู้ว่าเหลือหมวกสีดำ 2 ใบ และแดง 1 ใบ ดังนั้น ถ้าเขาตอบสีดำจะทำให้มีโอกาสตอบถูก 66% ในขณะที่สีแดงมีโอกาสแค่ 33% ซึ่งมันยังไม่ใช่ 100% ดังนั้นเขาก็ไม่ควรจะเป็นคนตอบ

    สุดท้าย E ก็จะรู้ว่าเหลือหมวกสีดำ 1 ใบ และแดง 1 ใบ ซึ่งโอกาสตอบถูกมีแค่ 50% ดังนั้นเขาก็ควรจะเงียบต่อไป

    จากที่ว่ามาทั้งหมดพอ E ไม่ยอมพูดอะไร ก็จะทำให้ D รู้คำตอบในทันทีว่า E เห็นหมวกสีแดงแค่ 1 ใบเหมือนกัน เพราะถ้าเห็น 2 ใบเขาต้องตอบแล้วแน่นอน เลยทำให้รู้ว่าตัวเขาเองต้องใส่หมวกสีดำแน่นอนกั๊ฟ

    สรุปสำหรับกรณีที่หมวกถูกกำหนดไว้แล้ว คนแรกที่จะเป็นคนตอบคำถามแล้วจะถูกต้อง 100% ก็คือ D นั่นเอง

    🎯 ข้อคิดที่ได้

    Information เป็นเรื่องสำคัญเพราะมันใช้ในการตัดสินใจทุกๆอย่าง แต่ในบางครั้งเราก็จำเป็นต้องอาศัย information จากสิ่งรอบข้าง เพื่อใช้ในการตัดสินใจให้ถูกต้องมากยิ่งขึ้นนั่นเอง

    ภาษาคน สมมุติว่าสัญญาณไฟจราจรฝั่งเราเขียวแล้ว แต่พบว่ารถคันด้านหน้าเราไม่ยอมไปเลยซักพัก เราก็ไม่ควรขับแซงเพื่อออกตัวไปก่อน เพราะรถคันด้านหน้าอาจจะเห็นบางอย่างที่ทำให้เขาตัดสินใจว่าไม่ควรออกไปก็ได้ (แมวน้ำเคยเกือบโดนรถชนเพราะมีเจ้าปิกาจู้ที่ไหนไม่รู้แหกไฟแดงมา ซึ่งเชื่อว่าหลายๆคนน่าจะเข้าใจความรู้สึกนี้ 👹)

    ภาษาขาเดฟ สมมุติว่าเราเห็นละว่า feature ที่จะเขียนนี้ไม่ได้ยากอะไรทำ 1-2 วันก็เสร็จ แต่ถ้าเพื่อนๆในทีมของเราอ้ำๆอึ้งๆตอนถามว่าจะใช้เวลาเท่าไหร่ เราก็อย่าด่วนไปตัดสินใจแทนให้ ควรถามเขาก่อนว่ามีประเด็นอะไร? เพราะบางทีสิ่งที่เขาคาใจอยู่อาจจะเป็นเรื่องที่เรายังคิดไม่ครบ หรือตัวเขาแอบวาง bug ไว้ ทำให้ต้องใช้เวลาเกินกว่าที่กำหนดก็ได้

    8.การตัดสินใจด้วย IF statements

    💬 หลังจากที่เราเปรียบเทียบค่าต่างๆเป็นละ คราวนี้เราจะลองให้โปรแกรมทำการตัดสินใจดูบ้าง เช่น จะเดินไปทางซ้ายหรือทางขวาดี ซึ่งการตัดสินใจของโปรแกรมเราเรียกมันว่า IF statements

    🎥 ตัวอย่างการใช้คำสั่ง IF statements

    🎯 สรุปสั้นๆ

    👨‍🚀 รูปแบบต่างๆของ IF statements

    คำสั่งในการตัดสินใจหรือ IF จริงๆมันก็มีแบบเดียวนั่นแหละ แต่เราสามารถเขียนมันได้ทั้งหมด 5 วิธีหลักๆตามด้านล่างครับ

    1.if

    1. if..else

    3.if..else..if

    4.Nested if

    5.Inline if หรือ short if

    คำเตือน การใช้ Inline if หรือ short if ไม่ใช่เรื่องความเท่ใดๆ ถ้าทีมที่เราทำงานด้วยไม่คุ้นเคยหรือไม่ชำนาญในการใช้คำสั่งลัดพวกนี้ ดช.แมวน้ำ ขอแนะนำว่าอย่าใช้ครับ เพราะจะทำให้เราทีมเสียเวลาและอาจะเกิดข้อผิดพลาดได้ง่ายขึ้นโดยใช่เหตุ แต่ถ้าทีมเริ่มมีความชำนาญมากขึ้นผมแนะนำให้ใช้เพราะมันทำให้โค้ดเรา clean ขึ้นครับ (ซึ่งผมจะขอยกเรื่อง Clean code ไปไว้ในบทของมันเองนะครับ)

    การใช้วงเล็บ การเขียน if หรือ else ไม่จำเป็นต้องใส่วงเล็บ { }ให้มันก็ได้นะครับ ซึ่งตัวโปรแกรมจะถือว่าคำสั่งถัดไป (แค่คำสั่งเดียว) คือของที่อยู่ในวงเล็บของมันครับ

    2019-08

    ✏️ ประวัติการอัพเดท

    31/08/2019

    28.Exception handler

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

    🎯 สรุปสั้นๆ

    Generic

    😢 ปัญหา

    ในบางครั้งที่เราเขียนโค้ด เราก็อาจจะไม่รู้ก็ได้ว่าเจ้าตัวแปรตัวนี้มันควรจะมี Data type เป็นอะไรดี เพราะมันขึ้นอยู่กับว่าคนที่เรียกใช้มันจะส่งอะไรมาให้นั่นเอง

    ตัวอย่าง: เราอยากมีคลาสที่เอาไว้เก็บข้อมูลอะไรก็ได้ไว้ 1 ตัว ซึ่งตอนแรกอาจจะเก็บค่าตัวเลขไว้ ก็จะได้โค้ดออกมาแบบนี้

    คำถามคือ แล้วถ้าเราอยากให้คลาสนั้นมันเก็บ double เข้าไปบ้างล่ะ หรืออาจจะเก็บ string บ้างล่ะ เราจะทำยังไงดี ?

    การลบข้อมูล

    🤔 เวลาจะลบข้อมูลควรทำยังไงกันนะ ?

    😥 ปัญหา

    บางทีข้อมูลที่เราลบไปแล้วก็ดันมีกรณีให้เราเอามันกลับมา และบางทีการลบข้อมูลก็ทำให้ database เร็วขึ้นและช้าลงได้ด้วยนะ แล้วเราจะแก้ยังไงหว่า ?

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

    Einstein's Riddle 01

    โจทย์ที่ไอสไตน์บอกว่ามีคนแค่ 2% บนโลกที่ตอบได้

    🥳 โจทย์

    ไอสไตน์ตั้งโจทย์ที่มีคนแค่ 2% บนโลกใบนี้ตอบได้ โดยเขาได้กล่าวว่ามันไม่ได้ยาก แต่คนส่วนใหญ่จะไม่มีความพยายามมากพอที่จะแก้โจทย์ข้อนี้ (แล้วแอบไปดูเฉลย อันนี้แมวน้ำกล่าว 🤣) โดยโจทย์คือ

    switch( EXPRESSION )
    {
      case PATTERN1:
         // ถ้า expression ตรงกับ pattern 1 จะเข้ามาที่งานที่นี่
         break;
      case PATTERN2:
         // ถ้า expression ตรงกับ pattern 2 จะเข้ามาที่งานที่นี่
         break;
      default:
         // ถ้า expression ไม่ตรงกับ pattern ไหนเลยจะเข้ามาทำงานที่นี่
         break;
    }
    switch( EXPRESSION )
    {
      case PATTERN1:
         {
           // ถ้า expression ตรงกับ pattern 1 จะเข้ามาที่งานที่นี่
           break;
         }
       ...
    }
    switch( EXPRESSION )
    {
      default:
      case PATTERN1:
      case PATTERN2:
      case PATTERN3:
         {
           // ถ้า expression ตรงกับ pattern 1,2,3 
           // หรือไม่ตรงกับ pattern อื่นๆเลยจะเข้ามาที่งานที่นี่
           break;
         }
       ...
    }
    switch( EXPRESSION )
    {
      case int a:
           // ถ้า expression เป็น int จะเข้ามาที่งานที่นี่
           break;
      case double b:
           // ถ้า expression เป็น double จะเข้ามาที่งานที่นี่
           break;
       ...
    }
    switch( EXPRESSION )
    {
      case int a when a > 12:
           // ถ้า expression เป็น int และมีค่ามากกว่า 12 จะเข้ามาที่งานที่นี่
           break;
       ...
    }
    public void MyMethod()
    {
       // Do something
    }
    public void MyMethod(int parameterA)
    {
       // Do something
    }
    public void MyMethod(int parameterA, string parameterB)
    {
       // Do something
    }
    public int MyMethod()
    {
       return 5;
    }
    public int Add(int firstValue, int secondValue)
    {
       return firstValue + secondValue;
    }
    public void MyMethod(out int parameter)
    {
       parameter = 99;
    }
    
    // ตอนเรียกใช้
    int a;
    MyMethod(out a); // พอทำงานเสร็จตัวแปร a จะมีค่าเป็น 99
    public void MyMethod(ref int parameter)
    {
       parameter = 99;
    }
    
    // ตอนเรียกใช้
    int a = 5;
    MyMethod(ref a); // พอทำงานเสร็จตัวแปร a จะมีค่าเป็น 99
    string firstName;
    string lastName;
    int age;
    string id;
    public class Student
    {
       public string FirstName;
       public string LastName;
       public int Age;
       public string Id;
    }
    Student s1 = new Student();
    s1.FirstName = "Sakul";
    s1.LastName = "Saladpuk";
    s1.Age = 18;
    s1.Id = "01";
    // แบบไม่มี initializer
    for( ; round < 10; round++ )
    {
      // Do something
    }
    
    // แบบมี initializer มากกว่า 1 ตัว
    for( int a = 1, b = 2; round < 10; round++ )
    {
      // Do something
    }
    for(int round = 0; ;round++)
    {
       // Do something
    }
    int a = 3;
    double b = a;
    double a = 3.33;
    int b = (int)a;
    int a = int.Parse("1");
    double b = int.Parse("3.33");
    int a = 3;
    string b = a.ToString();
    string c = 3.33.ToString();
    strubg d = "Hello".ToString();
    public class MyClass
    {
       public void MyMethod()
       {
       }
    
       public void MyMethod(int p)
       {
       }
    
       public void MyMethod(string p)
       {
       }
    
       public int MyMethod(int p)
       {
          return p;
       }
    }

    คำเตือน เพื่อนๆบางคนอาจจะบอกว่า งั้นก็กำหนดให้มันเป็น object ไปดิ จะเก็บอะไรก็เก็บได้หมดเบย!! คำตอบคือใช่ครับทำแบบนั้นก็ทำงานได้ แต่โค้ดแบบนั้นมันจะทำให้ performance ตกลง เพราะเราต้องไปแปลงของต่างๆให้ไปเป็น object และ ตอนที่เราจะเอาค่ามันกลับมา เราก็ต้องทำการแปลงกลับมาด้วย ซึ่งเราเรียกเรื่องนี้ว่าการทำ Boxing and Unboxing แนะนำว่าอย่าทำ

    แนะนำให้อ่าน การทำ Boxing and Unboxing นั้นจริงๆมันเป็นยังไง สามารถอ่านได้จากบทความนี้เลย 💡 Boxing & Unboxing

    😄 วิธีแก้ปัญหา

    เราสามารถใช้สิ่งที่เรียกว่า Generic มาช่วยแก้ปัญหาที่ว่าได้ โดยหลักการคือมันจะให้คนที่เรียกใช้เป็นคนกำหนดเองว่า data type ที่เขาจะทำงานด้วยคืออะไรนั่นเอง

    🔥 ใช้กับตัวแปร

    จากโค้ดตัวอย่างด้านบน เราก็สามารถเอา Generic เติมเข้าไปได้เลยเป็นแบบนี้

    จะเห็นว่าบรรทัดที่ 1 ถูกเพิ่ม <T> ต่อท้ายเข้าไป ซึ่งเจ้านี่แหละคือ generic และส่วนบรรดทัดที่ 2 แทนที่เราจะกำหนดให้เป็น int เราก็เอาเจ้า T จากบรรทัดที่ 1 มาใส่ไว้นั่นเอง อ่านแล้ว งงๆ อยู่ไม่เป็นไร ไปดูต่อกัน

    คราวนี้เวลาที่เราจะเรียกใช้คลาสนี้ เราก็จะต้องสร้าง object ของมันพร้อมกับบอกว่าเจ้าคลาสนี้จะทำงานกับ data type อะไรนั่นเอง เช่น อยากทำงานกับ int เราก็จะเขียนโค้ดออกมาเป็นแบบนี้

    จากโค้ดด้านบนจะเห็นว่าตอนที่สร้าง object ของคลาส SimpleStorage เราจะต้องบอกว่า เจ้าคลาสนี้จงทำงานกับ int ซะ (ในบรรทัดที่ 3) ดังนั้นตัวแปรที่ชื่อว่า Value เลยมี data type เป็น int นั่นเอง เลยทำให้มันเก็บค่าตัวเลขลงไปได้ (ในบรรทัดที่ 4)

    จากที่ว่ามาเราก็อาจจะสร้าง object ของคลาส SimpleStorage เพื่อให้มันทำงานกับ data type ที่เราอยากทำงานด้วยหลายๆแบบก็ได้ ตามโค้ดนี้เลย

    🔥 ใช้กับ Method

    นอกจากที่เราจะใช้กับตัวแปรได้แล้ว เรายังสามารถเอา Generic มาใช้กับ method ได้ด้วยนะ ซึ่งถ้าเราใช้กับ method เราสามารถกำหนด generic ไว้กับ method ได้เลย ประมาณนี้

    ซึ่งตอนเรียกใช้ก็จะราวๆนี้

    และรวมถึงเราจะเอาไปให้มันเป็น return type ก็ได้นะ

    🔥 Generic แบบหลายตัว

    ในบางทีเราก็อยากให้มันทำ generic ไว้หลายๆตัวเราก็สามารถทำได้นะ โดยการคั่น generic แต่ละตัวด้วยคอมม่ายังไงล่ะ

    คนเรียกใช้งานก็จะต้องเรียกราวๆนี้

    🤔 ขอตัวอย่างที่เอาไปใช้งานจริงๆหน่อย

    มีตรึมเลยที่ Microsoft เขาเขียนไว้เบื้องต้นให้เราแล้ว เช่นเราอยากเก็บข้อมูลในรูปแบบ Stack เราก็สามารถใช้คลาส Stack<T> ได้เลย

    🎯 บทสรุป

    ความสามารถของ generic จะช่วยให้ คนที่เรียกใช้งานเป็นคนกำหนดเองได้ว่า เขาอยากทำงานกับ data type อะไร ซึ่งจะช่วยทำให้โค้ดของเรามีความยืดหยุ่นมากขึ้น และนำกลับมาใช้ใหม่ได้มากขึ้นนั่นเอง

    แนะนำให้อ่าน ในการใช้งาน generic นั้นจริงๆมันทำได้เยอะม๊วกกกกก เช่นใช้กับ interface , method, delegate บลาๆ แนะนำให้ไปอ่านเอาต่อนะเพราะไม่งั้นบทความนี้จะยาวเป็นหางว่าวเลย Microsoft document - Generics

    public class SimpleStorage
    {
        public int Value;
    }
    public class SimpleStorage<T>
    {
        public T Value;
    }
    static void Main(string[] args)
    {
        SimpleStorage<int> s = new SimpleStorage<int>();
        s.Value = 55;
        Console.WriteLine(s.Value);
    }
    var s1 = new SimpleStorage<int>();
    s1.Value = 55;
    
    var s2 = new SimpleStorage<string>();
    s2.Value = "Hello world";
    
    var s3 = new SimpleStorage<bool>();
    s3.Value = false;
    public class Awesome
    {
        public void ShowIt<T>(T value)
        {
            Console.WriteLine(value);
        }
    }
    int a = 55;
    var awe = new Awesome();
    awe.ShowIt(a);
    public T ShowIt<T>(T value)
    {
        Console.WriteLine(value);
        return value;
    }
    public class Awesome<T,U>
    {
        public T Value1 { get; set; }
        public U Value2 { get; set; }
    }
    var awe = new Awesome<int, string>();
    awe.Value1 = 55;
    awe.Value2 = "Hello world";
    var stack = new Stack<int>();
    stack.Push(5);
    stack.Push(9);
    var lastedValue = stack.Pop();
    Console.WriteLine(lastedValue);  // 9

    เปรียบเทียบว่า ค่าด้านซ้าย น้อยกว่าหรือเท่ากับ ค่าด้านขวา ใช่หรือไม่

    น๊อท NOT

    !true ได้ false แต่ถ้า !false ได้ true

    Operator

    ความหมาย

    >

    เปรียบเทียบว่า ค่าด้านซ้าย มากกว่า ค่าด้านขวา ใช่หรือไม่

    <

    เปรียบเทียบว่า ค่าด้านซ้าย น้อยกว่า ค่าด้านขวา ใช่หรือไม่

    ==

    เปรียบเทียบว่า ค่าด้านซ้าย เท่ากับ ค่าด้านขวา ใช่หรือไม่

    !=

    เปรียบเทียบว่า ค่าด้านซ้าย ไม่เท่ากับ ค่าด้านขวา ใช่หรือไม่

    >=

    เปรียบเทียบว่า ค่าด้านซ้าย มากกว่าหรือเท่ากับ ค่าด้านขวา ใช่หรือไม่

    Operator

    ความหมาย

    ออกเสียง

    จำง่ายๆ

    &

    และ

    แอนด์ AND

    true และ true ได้ true

    |

    หรือ

    ออ OR

    ถ้ามี true ปุ๊ป ได้ true

    !

    <=

    เปลี่ยนผลลัพท์ให้เป็นค่าตรงข้าม

    เขียนบทความเรื่อง 👦 Test-First Design

    30/08/2019

    • เขียนบทความเรื่อง 👶 สิ่งที่คนเขียนโค้ดมักเข้าใจผิด

    • แยกระดับความยากของคอร์ส 👶 เขียนโปรแกรมด้วยภาษา C#

    29/08/2019

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 7 คลาว์คิดเงินยังไง ?

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 6 มาสร้าง Logic App กัน

    28/08/2019

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 5 ประเภทของคลาว์เซอร์วิส

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 4 สร้าง Virtual Machine กัน

    service-types

    27/08/2019

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 3 สร้างเว็บตัวแรกกัน

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 2 รู้จักกับ Resource Groups

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 1 สมัคร Microsoft Azure

    26/08/2019

    • เขียนคอร์ส 👶 Microsoft Azure 101 (ยังไม่เสร็จ)

    • เขียนบทความเรื่อง 👶 Cloud พื้นฐาน

    25/08/2019

    • เขียนบทความเรื่อง 👦 Test-First Design (ยังไม่เสร็จ)

    • เขียนบทความเรื่อง 👶 Clean Code

    • แก้ระดับความยาก SOLID Design Principles จาก 👶 >> 👦

    24/08/2019

    • เขียนบทความเรื่อง 👶 Code Smells

    • อัพเดท 👶 SOLID Design Principles บทที่ 5 Dependency-Inversion Principle (DIP)

    23/08/2019

    • อัพเดท 👶 SOLID Design Principles บทที่ 4 Interface Segregation Principle (ISP)

    • อัพเดท 👶 SOLID Design Principles บทที่ 3 Liskov Substitution Principle (LSP)

    • อัพเดท 👶 SOLID Design Principles บทที่ 2 Open/Closed Principle (OCP)

    • อัพเดท บทที่ 1

    • อัพเดทโค้ด Format ให้อ่านได้ง่ายขึ้นกับทุกคอร์ส

    22/08/2019

    • อัพเดท 👶 เขียนโปรแกรมด้วยภาษา C# บทที่ 24~30

    20/08/2019

    • อัพเดท 👶 เขียนโปรแกรมด้วยภาษา C# บทที่ 20~23

    19/08/2019

    • อัพเดท 👶 เขียนโปรแกรมด้วยภาษา C# บทที่ 17~19

    • สร้าง 🌐 Facebook blog ไว้แล้วนะฝากเข้าไปกดไลค์ + ติดตามด้วยนะฮ๊าฟฟฟ จะได้ไม่พลาดอัพเดท

    18/08/2019

    • เขียนคอร์ส 👶 SOLID Design Principles (ยังไม่จบ)

    • เขียนคอร์ส 👦 Web API

    17/08/2019

    • เขียนคอร์ส 👦 Test-Driven Development

    • เขียนคอร์ส 🤴 Design Patterns

    12/08/2019

    • เขียนคอร์ส 👶 เขียนโปรแกรมด้วยภาษา C# (ยังไม่จบ)

    • กำเนิด Saladpuk ใน Gitbook เย่ๆๆๆ 😆

    👨‍🚀 Exception handler คือ

    ตัวช่วยในการดักจับข้อผิดพลาดต่างๆในโปรแกรม ซึ่งเราจะใช้ตัวดักจับ error ไปครอบโค้ดที่มีความเสี่ยงว่ามันอาจจะเกิด error เอาไว้ ส่วนถ้ามันเกิด error ขึ้นจริงๆ เราก็จะสามารถเขียนโค้ดในการตัดสินใจต่อได้ว่าควรจะทำอะไรกับมันต่อ ตามโค้ดด้านล่าง

    👨‍🚀 การดักจับ exception แบบระบุ

    ตอนที่เกิด error ขึ้น เราสามารถเลือกได้ว่า "ถ้าเกิด error แบบ A เราจะจัดการแบบนี้ หรือ "ถ้าเกิด error แบบ B ให้จัดการแบบโน้น" ก็ได้เหมือนกันนะ โดยการระบุประเภทของ exception ลงไปใน catch ตามตัวอย่างด้านล่าง

    Exception คือต้นตระกูลของ exception ทุกตัว ดังนั้นถ้าเกิด error ที่เกิดขึ้น ไม่ตรงกับ catch ตัวไหนเลย มันจะเข้าไปที่ Exception

    Exception ถ้ามี catch หลายๆตัว ห้ามเอา Exception ไปไว้ใน catch ตัวแรก เพราะถ้ามันเกิด error ขึ้นมันจะเข้า catch(Exception e) ตัวแรกที่วางไว้โดยไม่สนใจ catch อื่นๆด้านล่างเลย

    👨‍🚀 Finally keyword

    คือ block ที่ระบุว่า ไม่ว่าจะทำแล้วเกิด error ขึ้นหรือไม่ หลังจากทำ try หรือ catch เสร็จ มันก็จะมาทำโค้ดใน block ของ finally เป็นลำดับถัดไปนั่นเอง

    👨‍🚀 throw keyword

    คือคำสั่งที่บอกว่า ณ โค้ดจุดนั้นจะไม่ขอจัดการ error นี้แล้ว และจะทำการส่ง error นั้นๆไปให้กับ โค้ดที่เรียกมันรับหน้าที่จัดการต่อ

    หรืออยู่ๆ เราอยากจะโยน error ออกจาก method ดื้นๆทั้งๆที่ยังไม่เกิด error ก็ได้ เช่น ผมเขียน method ที่รับชื่อเข้ามา ซึ่งถ้าส่งชื่อเข้ามามันจะทักทาย แต่ถ้าส่ง null หรือช่องว่างเข้ามามันจะส่ง error ออกไปแทน เราก็จะได้โคีดตามตัวอย่างด้านล่างนี้ครับ

    👨‍🚀 การส่งต่อ Exception โดยมี message

    เวลาที่เราส่งต่อ exception เราสามารถกำหนด message ให้กับ exception ที่ส่งต่อได้ เพื่อให้คนที่รับผิดชอบสามารถรู้ได้ว่า error ที่เกิดขึ้นนั้น เกิดจากอะไร ตามตัวอย่างด้านล่าง

    Exception message เวลาที่เราใส่ message ให้กับ exception object ลองกดดู overload ของมันด้วยนะ เพราะมันมี overload ให้เราเลือกเยอะม๊วก

    👨‍🚀 Custom Exception

    นอกเหนือจาก exception มาตรฐานที่ Microsoft เตรียมไว้ให้แล้ว ถ้าเรามี exception แบบพิเศษในแบบของเราเอง เราก็สามารถทำได้เหมือนกัน โดยการสร้างคลาสธรรมดานี่แหละไปสืบทอดจากพวก exception class

    คำเตือน โดยปรกติเราจะไม่สร้าง Custom exception เท่าไหร่นะ เพราะของที่มีมากับ .NET อยู่แล้วมันค่อนข้างจะสมบูรณ์ด้วยตัวมันเอง ผมแนะนำว่าลองศึกษา Exception ที่มีมาให้เสียก่อนว่าเพียงพอต่อการใช้งานของเราหรือเปล่า ซึ่งถ้าดูแล้วมันไม่เพียงพอต่อการใช้งานจริงๆเราค่อยทำการ Custom มันก็ได้ครับ

    👨‍🚀 ข้อควรระวังในการใช้ try/catch

    โดยปรกติการใช้ try/catch มันจะทำให้ความเร็วในการทำงานตกลง ดังนั้นในทางปฏิบัติจริงๆเราควรจะหลีกเลี่ยงการใช้ try/catch ให้ได้มากที่สุดเท่าที่จะทำได้ เช่นการตรวจสอบก่อนว่าค่านั้นๆเป็น null หรือเปล่า หรือตัวหารเป็นศูนย์หรือเปล่า ก่อนที่จะนำตัวแปรพวกนั้นไป execute จริงๆนะครับ ตามตัวอย่างโค้ดด้านล่างเลยเป็นการป้องกันตัวหารเป็นศูนย์

    😄 วิธีแก้ปัญหา

    แทนที่เราจะลบข้อมูลทิ้งทั้ง record จริงๆเราก็แค่ทำ Delete Flag ง่ายๆไว้แทนงุยล่ะ เช่น เรามีข้อมูลอยู่ในตารางประมาณนี้

    ไม่เกี่ยวกับการเมืองใดๆทั้งสิ้น รายชื่อทั้งหมดนั่นเป็นชื่อเพื่อนๆสมัยเด็กของแมวน้ำ ช่วงหัดว่ายข้ามแอตแลนติสกัน

    แล้วด้วยเหตุผลอะไรก็แล้วแต่ เรามีความจำเป็นต้องลบ records สีแดงทิ้งไป 2 ตัว ตามรูปด้านล่าง

    ก็บอกแล้วว่าไม่เกี่ยวกับการเมืองไง

    เราก็แค่สร้าง Delete Flag ขึ้นมาช่วยในการลบซะ โดยแมวน้ำแนะนำว่าควรจะใช้ วันที่และเวลา เป็น flag นะจ๊ะ ตามรูปด้านล่าง

    Delete Flag จะต้องเป็น nullable นะจ๊ะ

    จากตารางด้านบน เมื่อเราอยากจะเอาไปใช้เราก็แค่ filter เอาเฉพาะตัวที่ DeletionDate = null ไปใช้เท่านั้นยังไงล่ะ ส่วนถ้าใครซีเรียสขึ้นอีกหน่อยก็อาจจะเก็บลงไปต่อด้วยว่า ใครเป็นคนลบข้อมูลพวกนี้ต่อได้อีกนะ ตามรูปดเานล่างเบย

    เพียงเท่านี้ข้อมูลของเราก็จะไม่ได้หายไปจริงๆ แถมมี history record ที่เป็นประโยชน์ในการตรวจสอบย้อนหลังเพิ่มเข้ามาด้วยแล้วขอรับ และ ถ้าอยากจะย้อนสถานะกลับมาก็เพียงแค่กำหนดค่ากลับให้เป็น null ก็เป็นอันเรียบร้อย

    🤕 ข้อควรระวัง 1

    ตามบทความก่อนหน้าที่เคยบอกไปว่า ยิ่งฐานข้อมูลใหญ่เท่าไหร่มันจะยิ่งช้า ดังนั้นการทำแบบนี้สำหรับ database บางตัวและบางเวอร์ชั่น จะมีปัญหาในการทำแบบนี้ เพราะข้อมูลเราไม่เคยถูกลบเลยจริงๆจังๆนั่นเอง (Relational Database จะมีปัญหากับเรื่องนี้เยอะกว่า NoSQL) ดังนั้นเราควรมีการกำหนดเวลาลบข้อมูลที่ไม่มีการเคลื่อนไหวเป็นระยะเวลานานๆทิ้งไปด้วย

    ข้อควรระวัง การลบข้อมูลในที่นี่หมายถึงการย้ายข้อมูลที่ไม่มีการเคลื่อนไหวเป็นระยะเวลานานๆ ไปพักเก็บอีกที่ นะ ซึ่งเราเรียกข้อมูลพวกนั้นว่า Archive data เพราะวันดีคืนดี เราอาจจะต้องเอามันกลับมาใช้ก็ได้ เช่น ลูกค้าอยากจะดูรายการสั่งซื้อย้อนหลัง 10 ปีที่ผ่านมาไรงี้ ถ้าเราลบทิ้งทั้งหมดก็จบกัน และไม่ควรเก็บมันไว้ใน Hot data ด้วย

    เกร็ดความรู้ ข้อมูลที่มีอยู่ในระบบ ส่วนใหญ่เราจะแยกมันออกมาได้ 3 ประเภทตามด้านล่าง ซึ่งแต่ละประเภทก็จะมีการดูแลและความถี่ในการเรียกใช้งานที่ต่างกันครัช

    • Hot data - ข้อมูลมีการเรียกใช้บ่อยๆ เช่น ข้อมูลที่แสดงในหน้ารายการสินค้า ส่วนใหญ่เราจะคุ้นเคยกับตัวนี้อยู่แล้ว

    • Cool data - ข้อมูลถูกเรียกใช้ไม่ค่อยบ่อยเท่าไหร่ ส่วนใหญ่จะใช้สำหรับข้อมูลที่เก่าแต่ไม่เก่ามาก และ รวมถึงใช้เป็น backup data ด้วย

    • Archive data - ข้อมูลที่ไม่ถูกเรียกใช้เลย ส่วนใหญ่เป็นข้อมูลที่เก่าม๊วกๆๆ เช่น ข้อมูลสั่งซื้อที่ผ่านมาแล้ว 1 ปี ซึ่งข้อมูลพวกนี้ถ้าเก็บไว้ใน cloud จะถูกม๊วกๆ แต่ถ้าจะเรียกใช้มันจะมีค่าใช้จ่ายที่เยอะกว่าปรกติ แถมต้องรอมันดึงข้อมูลอาจจะ 30 นาทีหรือเป็นชั่วโมงเลยก็ได้

    ตามที่เขียนไว้ใน ข้อควรระวัง การลบมันจะต้องเป็นการย้ายจากที่นึงไปพักไว้อีกที่นึง ไม่ใช่ลบทิ้งถาวร (Hard delete) เพื่อเป็นการ backup เผื่อกรณีที่จำเป็นต้องเรียกใช้จริงๆนั่นเอง เพราะอย่าลืมว่าในสมัยนี้ ข้อมูลคือสิ่งที่สำคัญที่สุด ดังนั้นการมี data ไว้ก่อนย่อมดีกว่า (ถ้าไม่ใช่ data ขยะ ส่วนเรื่อง fresh data ก็ว่ากันไปตามเรื่องอะนะ) และอย่าลืมวางแผนคุยกันว่าจะย้ายจะลบอะไรบ้างนะ ไม่งั้นทีมตีกันตายแน่เลย

    🤕 ข้อควรระวัง 2

    การลบข้อมูลแบบถาวร (Hard delete) ในบางทีจะมีปัญหากับ DBMS ได้ เพราะตอนที่เราเขียนข้อมูล ตัวระบบมันจะจัดการให้ข้อมูลแต่ละอย่างมีการเรียงต่อกัน ซึ่งข้อมูลพวกนั้นตอนที่เอาไปเขียนลง physical disk จริงๆมันจะอยู่ address ใกล้ๆกัน ตามรูปด้านล่าง

    แต่เมื่อเรามีการลบข้อมูลนั้น สิ่งที่เกิดขึ้นก็จะเป็นแบบในรูปด้านล่างนี้

    ซึ่งจริงๆความหมายก็ไม่ได้มีอะไรผิดแปลกไปนะ แต่ตอนที่มันวิ่งไปอ่านข้อมูล ตัว OS มันจะต้องวิ่ง a01 แล้วกระโดดข้ามไปที่ a04 ยังไงล่ะ แถมถ้าเราเพิ่มข้อมูลเข้าไปใหม่ อีก 2 record มันอาจจะเกิดเป็นภาพนี้ก็ได้

    ดังนั้นเรื่องการ delete record ไปเยอะๆ ก็จะทำให้เกิดปัญหาในการ access address เพราะมันจะต้องกระโดดข้ามไปอ่าน pointer ในแต่ละจุดไปๆมาๆนั่นเอง ดังนั้นเราก็ควรจะมีวาระในการจัดการ disk ของเราด้วยนะจ๊ะ

    🤔 ทำแบบนี้มีข้อเสียป่าว ?

    แน่นอนครับของทุกอย่างย่อมมีด้านมืดและสว่างในตัวมันเอง ดังนั้นวิธีการทำ Delete Flag แบบนี้ก็มีข้อเสียที่เจอได้บ่อยๆนั่นก็คือ

    1. ชัดเจนที่สุดคือ เปลืองพื้นที่ เพราะมันไม่ถูกลบออกไปจริงๆ

    2. เวลาที่ดึงข้อมูลไปใช้เราจะต้องจัดการเอาตัวที่มี Delete Flag ออกก่อนเสมอ นั่นหมายความว่ามันจะต้องเปลือง Computing Power มากขึ้น และ มีโอกาสผิดพลาดสูงขึ้นถ้าลืมกัน

    3. การที่ข้อมูลไม่ถูกลบออกไปจริงๆ ทำให้เกิดปัญหาเรื่อง Reservation เช่น Username นี้จะไม่สามารถใช้ได้อีกเลย (กรณีนี้ขึ้นอยู่กับ business requirements ว่าจริงๆมันควรจะเป็นยังไง)

    4. ในบางกรณีที่มี constrain ว่าลบแม่แล้วลูกต้องถูกลบตายตามกันไป ถ้าเราใช้ Delete Flag นั่นหมายความว่าเราต้องมั่นใจในการทำ event sourcing เพื่อตามไป Flag ตัวลูกด้วยนั่นเอง

    เครดิต ดช.แมวน้ำ ต้องขอขอบคุณข้อแนะนำของท่าน Pramoth Suwanpech ด้วยขอรับ ที่ช่วยชี้แนะว่าบทความนี้ตกเรื่องอะไรไป จุ๊ฟๆ 😘

    👶 บทสรุปฐานข้อมูล
    The situation

    There are 5 houses in five different colors. In each house lives a person with a different nationality. These five owners drink a certain type of beverage, smoke a certain brand of cigar and keep a certain pet. No owners have the same pet, smoke the same brand of cigar or drink the same beverage.

    Hints

    1. The Brit lives in the red house

    2. The Swede keeps dogs as pets

    3. The Dane drinks tea

    4. The green house is on the left of the white house

    5. The green house's owner drinks coffee

    6. The person who smokes Pall Mall rears birds

    7. The owner of the yellow house smokes Dunhill

    8. The man living in the center house drinks milk

    9. The Norwegian lives in the first house

    10. The man who smokes blends lives next to the one who keeps cats

    11. The man who keeps horses lives next to the man who smokes Dunhill

    12. The owner who smokes BlueMaster drinks beer

    13. The German smokes Prince

    14. The Norwegian lives next to the blue house

    15. The man who smokes blend has a neighbor who drinks water

    The question is: Who owns the fish?

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

    1. คนอังกฤษอยู่บ้านสีแดง

    2. คนสวีเดนเลี้ยงหมา

    3. คนเดนมาร์กชอบดื่มชา

    4. บ้านสีเขียวอยู่ทางฝั่งซ้ายของบ้างสีขาว

    5. เจ้าของบ้านสีเขียวชอบดื่มกาแฟ

    6. คนที่สูบบุหรี่ยี่ห้อ Pall Mall เลี้ยงนก

    7. เจ้าของบ้างสีเหลือสูบบี่หรี่ยี่ห้อ Dunhill

    8. คนที่อยู่บ้านตรงกลางชอบดื่มนม

    9. คนนอเวย์อาศัยอยู่บ้านหลังแรก

    10. คนที่ชอบสูบบุหรี่แบบพันเองอาศัยอยู่ติดกับคนที่เลี้ยงแมว

    11. คนที่เลี้ยงม้าอาศัยอยู่ติดกับคนที่สูบบุหรี่ยี่ห้อ Dunhill

    12. เจ้าของบ้านที่ชอบสูบบุหรี่ BlueMaster ชอบดื่มเบียร์

    13. คนเยอร์มันชอบสูบบุหรี่ยี่ห้อ Prince

    14. บ้านของคนนอเวย์อยู่ติดกับบ้านสีน้ำเงิน

    15. คนที่ชอบพันบุหรี่เองมีเพื่อนบ้านข้างๆที่ชอบดื่มน้ำ

    คำถามคือใครคือเจ้าของบ้านที่เลี้ยงปลา?

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

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของ 🧠 Challenges ที่จะคอยรวบรวมโจทย์ที่น่าสนุกคิดเพลินๆ หากใครสนใจอยากดูว่ามีโจทย์อะไรบ้างก็อัญเชิญกดที่ชื่อสีฟ้าๆไปเสพต่อได้เบย ส่วนใครที่คิดว่ามีโจทย์น่าสนใจก็สามารถส่งมาได้ที่ Saladpuk Fanclub นะกั๊ฟ 😘

    🤠 วิธีคิด

    เดี่ยวมาเฉลยเย็นๆวันศุกร์นะ งานเยอะมากช่วงนี้

    internal

    ภายใน assembly เดียวกันเท่านั้นถึงใช้งานได้

    5

    protected internal

    ภายใน assembly เดียวกัน หรือ คลาสลูกที่ต่าง assembly เท่านั้น

    6

    private protected

    ภายใน assembly เดียวกัน หรือคลาสลูกเท่านั้น

    Microsoft document
    🧠 Challenges
    Saladpuk Fanclub
    ไม่ต้องห่วง รูปพวกนี้แมวน้ำทำ scale มาถูกต้อง 😁
    เสาทั้งสองต้องติดกัน
    คิดแบบตรรกะจำแบบโปรแกรมเมอร์

    อ่านเรื่องไรดี ?

    บทความเริ่มเยอะละ จะเริ่มอ่านเรื่องไรดีนะ

    อย่างที่เห็นว่าบทความที่เมนูด้านซ้ายมันเริ่มเยอะขึ้นไปเรื่อยๆละ ดังนั้นผมขอจัดหมวดหมู่มันไว้หน่อยละกัน เพื่อนๆจะได้เลือกศึกษาเฉพาะเรื่องที่กำลังสนใจได้ง่ายๆครับ

    ☁️ Cloud Computing

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

    🤖 AI + Chat bot + Machine Learning

    เจ้าพวกนี้มันค่อยๆแผงตัวเข้ามาในชีวิตประจำวันเราเรื่อยๆละ อีกทั้งมันยังทำให้แอพต่างๆของเรามีลูกเล่นที่เอาไปต่อยอดทางธุรกิจได้อีกเยอะเบย ดังนั้นคอร์สและบทความที่เกี่ยวข้องกับมันจะอยู่ในนี้

    📝 Software Development

    ความรู้ในการสร้างซอฟต์แวร์โปรเจค และสิ่งที่คนเขียนโค้ดควรจะต้องรู้

    🗃️ Database

    การออกแบบฐานข้อมูลฉบับคนไม่รู้เรื่องอะไรเลย

    • 🗃️

    • 🗃️

    🧪 Software Testing

    หลักในการทดสอบซอฟต์แวร์ขั้นต่ำสุดที่ควรจะต้องทำ เพื่อป้องกันบัคที่แอบอยู่ในโค้ดของเรา

    • 🧪

    • 🧪

    📐 Software Designs

    หลักในการออกแบบซอฟต์แวร์ที่ดี พื่อทำงานให้ได้งานที่สามารถปรับปรุงแก้ไขเพิ่มเติมได้ดั่งใจหมาย

    • 📐

    • 📐

    • 📐

    • 📐

    👨‍👩‍👦‍👦 บริหารจัดการ ทีม + งาน

    การรับมือกับปัญหาเวลาทำงานเป็นทีม

    • 👨‍👩‍👦‍👦

    • 👨‍👩‍👦‍👦

    • 👨‍👩‍👦‍👦

    🚀 Microservices Architecture

    หลักในการออกแบบซอฟต์แวร์โดยการแยกส่วนการรับผิดชอบออกเป็นงานย่อยๆ

    • 🚀

    • 🚀

    🔐 Security

    ทำซอฟต์แวร์ต้องรู้เรื่องความปลอดภัยอะไรบ้างนะ ?

    • 🔐

    🔗 Blockchain

    • 🔗

    🐳 Docker

    • 🐳

    ⛩️ API

    5.คำสั่งพื้นฐาน

    💬 หลังจากเราสร้างตัวแปรได้ละ คราวนี้เราลองเอาตัวแปรที่เราสร้างไว้มาลองเล่นกับมันดูหน่อยละกัน ซึ่งการที่เราจะเล่นกับตัวแปรของเราเราจะต้องรู้จักคำสั่งพื้นฐานของมันก่อนนะ

    ก่อนไปต่อ ดช.แมวน้ำ ขอทิ้งโจทย์เล่นๆไว้ 1 ข้อละกันนะว่า 2 + 12 / 2 x 3 - 1 ได้เท่าไหร่เอ่ย ?

    กฎของคณิตศาสตร์กับกฎของคอมพิวเตอร์มันใช้กฎเดียวกันนะ ดังนั้นคำตอบมีเพียงหนึ่งเดียว คือคนร้ายอยู่ในกลุ่มพวกเรา ผมขอเอาชื่อ คิงจูเลียต เป็นเดิมพันเลย!!

    คำสั่งพื้นฐาน ในโลกของการเขียนโปรแกรมเราเรียกมันว่า operator นะ

    🎯 สรุปสั้นๆ

    👨‍🚀 ชุดคำสั่งพื้นฐาน

    ข้อมูลประเภทตัวเลข

    • ไม่สามารถนำชนิดข้อมูลขนาดใหญ่ไปใส่ตัวแปรที่ชนิดข้อมูลมีขนาดเล็กกว่าได้

    ข้อมูลประเภท string

    • ถ้าใช้คำสั่ง + จะเป็นการนำข้อมูลมาต่อกัน เช่น "5" + 7 จะได้ผลลัพท์คือ "57"

    • คำสั่ง - * / จะไม่สามารถใช้กับ string ได้

    👨‍🚀 ชุดคำสั่งลัด

    👨‍🚀 ลำดับการทำงานของคณิตศาสตร์

    เวลาเจอเครื่องหมายทางคณิตศาสตร์หลายๆตัวพร้อมๆกัน โปรแกรมจะไล่ทำตามลำดับของตารางด้านล่างนี้

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

    เช่น 12 / 2 * 3 กรณีนี้เจอ หาร ก่อน (จากซ้ายไปขวา) ดังนั้นคำตอบคือ 18

    เฉลย ที่ถามว่า 2 + 12 / 2 x 3 - 1 = ? คำตอบคือ 19 นะจุ๊

    Abstraction

    🤔 มันคืออะไร ?

    คำว่า Abstraction มีใช้อยู่ในหลายวงการเลย แต่ในวงการซอฟต์แวร์ใน Wikipedia ถูกเขียนไว้ว่า

    Abstraction, in general, is a fundamental concept to computer science and software development[4]. The process of abstraction can also be referred to as modeling and is closely related to the concepts of theory and design[5]. Models can also be considered types of abstractions per their generalization of aspects of reality. (Wikipedia)

    ซึ่งถ้าถอดความหมาย เราก็จะได้หัวใจสำคัญของมันออกมาว่า

    Abstraction เป็นการสร้าง Model ให้สอดคล้องกับโลกของความเป็นจริง

    🤨 ก็ยัง งง อยู่ดีขอตัวอย่างหน่อย

    ในการทำ abstraction นั้นมันมีของอยู่ 3 อย่างให้เราต้องคิดในการสร้าง Model ซึ่งพูดไปก็จะ งง อีก ดังนั้นไปรู้จักมันพร้อมกับตัวอย่างกันเลยละกัน

    🔥 องค์ประกอบ

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

    1. ซองจดหมาย (Envelope)

    2. ตัวจดหมาย (Letter)

    3. คนส่งจดหมาย (Mailman)

    ซึ่งเราก็จะเอาองค์ประกอบเหล่านี้แปลงมาเป็น Class ที่โปรแกรมเมอร์เข้าใจ ดังนั้นเราก็จะได้ class ออกมา 3 ตัว

    🔥 Property & Behavior

    ในแต่ละองค์ประกอบเราจะต้องคิดต่อว่า มันต้องมีอะไรอยู่ในนั้นบ้าง (property) และมันทำอะไรได้บ้าง (behavior) อีกด้วย ดังนั้นเราก็จะกลับมามองต่อว่า

    • ซองจดหมาย - มันจะต้องมีการระบุว่า ผู้ส่งเป็นใคร ผู้รับเป็นใคร และส่งไปที่ไหน (properties)

    • ตัวจดหมาย - มันควรจะต้องมี หัวเรื่อง กับ เนื้อหา (properties)

    • คนส่งจดหมาย - เขาควรที่จะ รวบรวมซองจดหมายเพื่อเตรียมไปส่ง (behavior)

    ดังนั้นเราก็จะเอามาเติมใส่ใน class ของเราต่อ ก็จะออกมาเป็น

    🔥 ทำงานร่วมกัน

    สุดท้ายเราก็จะมองกลับมาว่า ของพวกนั้นมันจะทำงานร่วมกันยังไง นั่นเอง โดยโลกของความเป็นจริง เราก็จะเอา ตัวจดหมาย ใส่ ซองจดหมายนั่นเอง เลยทำให้เราได้โค้ดตามด้านล่าง

    จากที่ว่ามาทั้งหมดนี่แหละคือเรื่องการทำ Abstraction ดังนั้นผมสามารถพูดในอีกแง่นึงว่า หลักในการทำ abstraction มันคือการ นำของใน physical แปลงมาอยู่ในรูปแบบของ class เพื่อให้เหล่าโปรแกรมเมอร์สามารถเข้าใจการทำงานได้ง่ายขึ้น และ ของแต่ละอย่างก็จะถูกแบ่งสัดส่วนที่ชัดเจน

    ❓ แล้วสร้างคลาสไปทำไม ?

    สุดท้ายในโลกของ OOP เราก็จะเอา class เหล่านั้นไปสร้างเป็น object เพื่อใช้งานต่อ เลยทำให้เขานิยมเรียกเจ้าคลาสที่สร้างออกมาว่า พิมพ์เขียว (Blueprint) นั่นเอง

    🔎 มุมมอง

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

    ตัวอย่าง จดหมายในมุมของโรงงานผลิต เขาก็มีคล้ายๆกับที่เคยเขียน เช่น ซองจดหมาย

    แต่ในมุมมองของโรงงานผลิต เขาอาจจะมองว่า มันทำมาจากวัสดุอะไร ขนาดเท่าไหร่ ลวดลายเป็นแบบไหน ก็ได้

    จากที่ว่ามาก็จะเห็นแล้วว่า แม้จะเป็น ซองจดหมายเหมือนกัน แต่เมื่ออยู่ต่าง Context แล้วล่ะก็ มันอาจจะเป็นคนละเรื่องกันเลยก็ได้

    คำเตือน Abstraction ในที่นี้ ไม่ใช่ abstract keyword ที่ใช้ในการสร้าง abstract class นะขอรับ ซึ่งเรื่องนี้เข้าใจกันผิดได้บ่อยๆเพราะมันเป็นคำเดียวกัน แต่จำง่ายๆว่า abstraction ของ OOP คือ

    การนำของที่เป็น Physical แปลงมาเป็น Conceptual หรือที่เราเรียกว่า Modeling นั่นเองครัช

    หมายเหตุ ในการที่เราจะสร้าง Model ได้นั้น เราจะต้องใช้วิธีการคิดแบบ Abstraction เพื่อได้ให้สิ่งต่างๆที่สามารถทำงานได้ และมีความสัมพันธ์กันออกมา แต่ Model ที่ได้ออกมา มันจะยังไม่ใช่ของที่ดี ถ้าขาดการนำหลักการของ Encapsulation มาใช้งาน

    แนะนำให้อ่าน ในเรื่องของการออกแบบโดยดูจาก Context สามารถไปศึกษาต่อได้ในเรื่องของ Domain Driven Design หรือที่เราเรียกกันติดปากว่า DDD นั่นเองครัช

    OOP

    🤔 หลักพื้นฐานในการเขียนโค้ดด้วยแนวคิดแบบ OOP เขาคิดกันยังไง ?

    ผมเชื่อว่าหลายคนที่เขียนโค้ดมาได้ซักระยะก็จะได้ยินคำว่าการเขียนโปรแกรมแบบ Object-Oriented Programming หรือที่เรียกกันติดปากว่า OOP นั่นเอง หรือบางคนก็เคยเรียนผ่านมาบ้างแต่ก็อาจจะยังไม่แน่ใจว่าสิ่งที่เขียน สิ่งที่ใช้อยู่นั้นมันถูกต้องตามหลักของ OOP หรือยัง ดังนั้นในคอร์สนี้เราจะมาทำความเข้าใจกันว่า จริงๆแล้ว OOP มันคืออะไร ใช้งานยังไง และ มันสำคัญหรือเปล่า กันครัช

    🤨 เคลียความเข้าใจกันนิส

    ตั้งแต่เริ่มเรียนเขียนโค้ดครั้งแรก ผมเชื่อว่า 99.99% ทุกคนน่าจะได้ทำโจทย์หลายๆแบบ เช่น ตัดเกรด คำนวณหาพื้นที่ หรืออะไรก็ตามแต่ที่จะช่วยฝึกให้เราเข้าใจคำสั่งต่างๆ เช่น if..else, switch..case, loop บลาๆ กันมาใช่ไหม? แล้วพอผ่านมาซักระยะเราก็จะได้รับโจทย์ที่ยากขึ้น เช่นเขียนโปรแกรมเครื่องคิดเลข ทำระบบจองโรงแรม หรืออะไรก็ตามที่ไม่ได้เขียนจบภายในไฟล์เดียวอีกต่อไป

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

    🤔 Procedural Programming คือไย ?

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

    ยกตัวอย่าง เราจะเขียน โปรแกรมส่งจดหมาย เราก็จะคิดเป็นขั้นตอนว่า

    1. จะสร้างจดหมายขึ้นมา

    2. ทำการส่งหมายผ่าน function A ตัวนี้

    3. function A ก็จะส่งข้อมูลต่อไปให้กับ function B เพื่อติดต่อออกไปผ่าน SMTP

    4. บลาๆ

    ซึ่งวิธีการเขียนโค้ดแบบ Procedural Programming นั้นมันจัดการอะไรต่างๆค่อนข้างยาก เวลาอยากจะแก้ไขอะไรซักอย่างก็ต้องไปไล่หาว่ามันอยู่ขั้นตอนไหน และแต่ละขั้นตอนก็อาจจะไปพันกับอีกขั้นตอน ทำให้แก้จุดนี้ก็มีผลกระทบกับจุดอื่นๆด้วย ดังนั้นเลยมีการคิดวิธีการเขียนโปรแกรมอีกแบบที่เรียกว่า Object-Oriented Programming ขึ้นมา

    สรุปสั้นๆ หลักในการคิดของ Procedural Programming คือ คิดเป็นขั้นตอน สั่งงานตามขั้นตอนที่คิดไว้ โดยเราจะทำการ สร้าง step ต่างๆเอาไว้เรียกใช้นั่นเอง

    🤔 Object-Oriented Programming คือไย ?

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

    ยกตัวอย่าง เราจะเขียน โปรแกรมส่งจดหมาย เราก็จะคิดออกมาว่ามันมีองค์ประกอบอะไรบ้าง เช่น

    • ถ้าจะส่งจดหมายได้จะต้องมี ซองจดหมาย ที่ระบุคนรับคนส่ง

    • ถ้าจะส่งจดหมายจะต้องมี ตัวส่งจดหมาย ที่จะสามารถเก็บ จดหมาย ไว้ข้างในได้

    • บลาๆ

    ซึ่งจากตัวอย่างเราจะเห็นว่า การเขียนโปรแกรมแบบ OOP นั้นใกล้เคียงกับโลกของความเป็นจริง มากกว่าการเขียนแบบ Procedural Programming ดังนั้นถ้าเหล่า developer เขียนโค้ดโดยใช้หลักของ OOP มันก็จะทำให้เราสามารถเข้าใจกันได้เร็วขึ้น และโค้ดมีการแบ่งงานกันชัดเจน เวลาแก้ไขปัญหาก็พอจะรู้ว่าจะต้องไปแก้ที่จุดไหน

    สรุปสั้นๆ หลักในการคิดของ Object-Oriented Programming คือ มองของต่างๆเป็นเรื่องๆ ที่สอดคล้องกลับปัญหาที่เราจะแก้ไข

    🤼 เคลียกันรอบสุดท้าย

    การเขียนโปรแกรมไม่ว่าจะเป็น Procedural Programming หรือ Object-Oriented Programming สุดท้ายมันก็เป็นแค่วิธีการเขียนโปรแกรมแบบหนึ่งเท่านั้น และในโลกของการเขียนโปรแกรมก็ไม่ได้มีแค่นี้นะ เพราะในสมัยนี้มันมีอีกยั้วเยี้ยเลยให้เราเลือกศึกษา ซึ่งแต่ละวิธีก็จะมีข้อดีข้อเสียของมันเองทั้งนั้น

    🥺 แล้วจะรู้เรื่อง OOP ไปทำไม ?

    แม้ว่า OOP จะเป็นแค่หนึ่งในวิธีการคิดในการเขียนโปรแกรม แต่มันก็เป็นวิธีที่คนทั่วโลกนิยมใช้กัน แหละมันเหมือนกับเป็นเกณฑ์ตัวแรกเลยที่เขาวัดว่าเขาจะรับเราเข้าร่วมงานหรือเปล่าก็ว่าได้ ดังนั้นเรียนเจ้าตัวนี้ไปก็ไม่เสียหายหรอก เพราะสุดท้ายถ้าเราเข้าใจหลักในการเขียน OOP อย่างถ่องแท้แล้ว มันก็จะช่วยเป็น guideline ให้เราออกแบบโค้ดได้อย่างมีประสิทธิภาพและเป็นมืออาชีพในวงการนี้มากยิ่งขึ้นครับ 👍

    😁 เข้าเรื่องของ OOP กัน

    ในการเขียนโค้ดโดยใช้หลักของ OOP นั้น เราจะต้องดูก่อนว่าภาษาที่เราใช้อยู่มันรองรับ OOP หรือเปล่าด้วย ซึ่งโดยปรกติภาษาส่วนใหญ่จะทำ OOP ได้อยู่แล้วนะดังนั้นสบายใจได้ (C# รองรับ OOP 100%) โดยวิธีการดูว่าภาษาของเรารองรับ OOP หรือเปล่าคือ มันจะต้องทำ 4 เสาหลัก หรือเราเรียกว่า 4 Pillars ด้านล่างนี้ได้

    💖 4 Pillars of OOP

    4 เสาหลักที่เป็นตัววัดว่าภาษานั้นรองรับการทำ OOP หรือเปล่า และเป็นตัว guideline ในการออกแบบโค้ดที่เราจะนำมาใช้ในการแก้ปัญหาคือ

    1. Abstraction

    2. Encapsulation

    3. Inheritance

    4. Polymorphism

    ซึ่งแต่ละเรื่องผมจะขออธิบายแยกเป็นเรื่องๆของมันไปเน่อ ดังนั้นสนใจเรื่องไหนก็ลองไปจิ้มอ่านเอาที่ตัวด้านล่างได้

    🧭 เนื้อหาทั้งหมดของคอร์สนี้

    ใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

    คำเตือน การศึกษาเรื่อง OOP เป็นเรื่องที่ดี แต่การที่เราจะเข้าใจมันได้อย่างถ่องแท้นั้น เราจะต้องลองฝึกออกแบบ ลองเจอโจทย์เยอะๆ ลองผิดลองถูกจนถึงจุดหนึ่ง แล้วประสบการณ์เหล่านั้นมันจะช่วยทำให้เรากลับมาอ่าน concept พวกนี้แล้วถึงจะบรรลุหลักการพวกนี้อย่างแท้จริง ไม่อย่างนั้นแล้วเราก็จะเข้าตามตัวอักษร อาจเอาไปใช้ตอบเพื่อให้ผ่าน certificate ได้ แต่สุดท้ายเมื่อเจอโจทย์ในงานจริง คุณก็จะงูๆปลาๆอยู่ดี

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

    • 🧪

    • 🧪

    บทสรุปฐานข้อมูล

    🤔 จะทำงานได้ต้องรู้เรื่องฐานข้อมูลแค่ไหนกันนะ ?

    ในรอบนี้ ดช.แมวน้ำ จะมาสรุป ความรู้ขั้นต่ำสุด ในการทำงานกับฐานข้อมูล ในระดับที่เอาไปทำงานจริงได้ ให้เพื่อนๆได้เข้าใจกันว่า การทำงานและการออกแบบฐานข้อมูล เขามีวิธีคิด วิธีออกแบบยังไงบ้าง ซึ่งคนไม่มีความรู้เรื่องนี้เลยก็สามารถเข้าใจได้ง่ายๆแน่นอนฮ๊าฟ

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

    เกร็ดความรู้ Database ในโลกปัจจุบันนั้นมีทั้งหมด 4 ตระกูลหลัก Graph, Key-Value store, Document database, Column store ซึ่งแต่ละตระกูลนั้นมันเก่งคนละด้านกัน ซึ่งในรอบนี้เราจะมาเรียนการออกแบบโดยใช้ database ที่ทั่วโลกยังคงนิยมใช้กันอยู่นั่นคือ Relational Database นั่นเอง ซึ่งถ้าเราเข้าใจการออกแบบตัวนี้แล้ว ก็น่าจะแก้ปัญหาได้ 90% ของโลกใบนี้ได้แล้วล่ะ ส่วนที่เหลืออีก 10% คือกรณีพิเศษที่เราจำเป็นต้องไปใช้ตระกูลอื่นๆนั่นเอง ดังนั้นเรียนตัวนี้ไปก่อนก็ไม่เสียหาย เพราะมันยังเป็นพื้นฐานในการคิดของตระกูลอื่นๆด้วยนั่นเอง

    🗃️ ฐานข้อมูลคือไย ?

    เวลาที่เราทำแอพอะไรก็ตามแต่ ถ้าแอพของเรามีการบันทึกข้อมูลของผู้ใช้เอาไว้ เช่น เก็บชื่อผู้ใช้ เก็บที่อยู่ บลาๆ นั่นหมายความว่าเราต้องเอาข้อมูลพวกนั้นไปเซฟเก็บไว้ซักที่ ชิมิ? โดยที่ๆเราเอาข้อมูลพวกนั้นไปเซฟไว้นั่นแหละคือ ฐานข้อมูลรูปแบบหนึ่งนั่นเอง เช่น บันทึกเป็นไฟล์เก็บไว้ หรือเชื่อมต่อไปยัง Database ชนิดต่างๆยังไงก็ตาม สิ่งที่แรกเราควรรู้ก่อนทำงานกับฐานข้อมูลก็คือ 💖 วิธีการออกแบบฐานข้อมูล ซึ่งขั้นตอนในการออกแบบ ดช.แมวน้ำ ได้แยกออกเป็นเรื่องๆด้านล่างไว้เรียบร้อยแล้วครัช

    1.มโน

    การออกแบบ Database เริ่มต้นที่การ ลองมโนสิ่งที่เก็บในระบบออกมาแล้วลองเขียนลงกระดาษเล่นดู ซึ่งอยากฟุ้งซ่านแค่ไหนก็จัดเต็มได้เลย ตามในวีดีโอด้านล่าง

    2.จัดกลุ่มกำจัดขยะ

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

    ตัดๆทิ้งไปก่อน ถ้ามันจะเป็นเดี๋ยวมันก็กลับมาเอง ส่วนที่ลิสต์ไว้จะถูกกลุ่มหรือเปล่าก็ช่างมัน เดี๋ยวทำไปเรื่อยๆก็จะรู้เองเช่นกัน

    3.จำลองข้อมูลเล่นดู และ กำหนดคีย์หลัก (Primary key)

    หลังจากที่จัดกลุ่มเสร็จหมดละ ถัดไปเราก็จะ จำลองข้อมูล กันเล่นๆ เพื่อที่จะดูว่าสิ่งที่เราคิดไว้มันดูแล้วโอเคหรือเปล่า ซึ่งถ้าดูแล้วมันพอที่จะใช้ได้อยู่ ถัดไปเราก็จะต้องทำการสร้าง Primary Key ให้มัน เพื่อให้เราสามารถสั่งดำเนินการต่างๆได้ถูก record ที่เราอยากจะทำ

    Primary Key คือ คีย์หลัก ที่เป็นตัวแทนอ้างถึงข้อมูล record นั้นๆ ซึ่งมันจะต้องมีคุณสมบัติคือ Unique หรือพูดง่ายๆคือ มันห้ามซ้ำกับใครเลยนั่นเอง

    Candidate Key คือ คีย์ตัวเลือก มันคือข้อมูลทีมีคุณสมบัติเหมือน Primary Key ทุกประการ เพียงแค่เราไม่ได้เลือกมันเป็น Primary Key เฉยๆ เช่น เวลาเราเป็นนักเรียน เราก็จะมีรหัสประจำตัวนักเรียนชิมิ? ซึ่งรหัสประจำตัวนักเรียนแต่ละคนก็ไม่ซ้ำกันเลย และ เราใช้รหัสประจำตัวนักเรียนเป็น Primary Key เพราะเวลาคุณครูจะพูดถึงเรา เขาก็จะพูดรหัสประจำตัวนักเรียนยังไงล่ะ แต่จริงๆแล้วเราก็มี รหัสประชาชน อยู่เหมือนกัน ซึ่งมันก็ไม่ซ้ำกับใครเลยในโรงเรียนของเราเช่นกัน แต่ในโรงเรียนไม่นิยมเรียกนักเรียนด้วยรหัสประชาชน ดังนั้นในกรณีนี้ รหัสประชาชนเลยเป็นแค่ Candidate Key เท่านั้น

    4.ความสัมพันธ์ของตาราง

    คราวนี้เราก็จะมาดูความสัมพันธ์ของตารางแต่ละตัวกัน ซึ่งหลักๆจะมีทั้งหมด 3 รูปแบบคือ 1:1 , 1:Many และ Many:Many โดยที่ความสัมพันธ์แต่ละรูปแบบนั้นก็จะมีวิธีในการออกแบบที่แตกต่างกันไป ตามในวีดีโอด้านล่าง

    5.Normalization

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

    เกริ่น

    1st Normal Form

    2nd & 3rd Normal Forms

    บทความนี้กำลังทำอยู่ คาดว่าจะเสร็จราวๆวันอาทิตย์ที่ 23/02/2020 นี้แหละ ส่วนใครที่ไม่อยากพลาดอัพเดทก็เข้าไปกดติดตามที่ลิงค์นี้ ได้เลย และถ้าช่วยกดแชร์ด้วยจะเป็นพระคุณอย่างมากครัช

    วีดีโอตัวถัดไปที่จะทำ

    • Tips การใช้งานจริง

      • Denormalization

      • Bottlenecks

    Database indexing

    🤔 ฐานข้อมูลช้าทำไงดีฉบับเบื้องต้น

    😢 ปัญหา

    ดช.แมวน้ำ เชื่อว่าหลายคนที่ทำงานกับ database มาซักพัก ก็น่าจะมีความความรู้สึกว่า เธอไม่เหมือนเดิม ยิ่งนานวันยิ่งช้า ยิ่งอืด แข็งข้อบอกอะไรก็ไม่ค่อยจะยอมทำให้ และวันดีคืนดีก็หยุดทำงานไปดื้อๆซะอย่างนั้น แล้วเราจะแก้ปัญหาเหล่านี้ยังไงดีน๊าาาา (ผมพูดถึง database ไม่ได้อ้างถึงเมียแต่ประการใด หยุดส่งไปหาเมียผมได้แล้ว 🤕)

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

    😄 วิธีแก้ปัญหา

    สาเหตุที่ทำให้เกิดปรากฎการณ์ที่ว่ามา จริงๆมันมีหลายปัจจัยเลย แต่โดยพื้นฐานที่คนทั่วไปนิยมทำก็คือการทำ Database indexing นั่นเอง เพราะมันทำได้ง่าย ไม่ต้องพึ่งพาใคร ไม่ต้องเขียนโค้ดเพิ่ม และ มันเร็วขึ้นจริง

    แนะนำให้อ่าน เรื่องการทำ database indexing มันเป็นแค่ตัวเลือกหนึ่งที่ช่วยให้มันเร็วขึ้นได้ แต่มันเป็นการแก้ไขปัญหาที่ปลายเหตุ ซึ่งพอผ่านไปซักพักใหญ่ๆ เดี๋ยวมันก็จะกลับมาช้าเหมือนเดิมแหละถ้าต้นสาเหตุของมันไม่ถูกแก้ไข ซึ่งถ้าเพื่อนๆสนใจอยากรู้ว่าปัญหามันเกิดจากอะไรได้บ้าง ลองไปศึกษาได้จากลิงค์นี้เบย อยู่ๆแอพที่ทำก็ช้าเป็นเต่าเฉยเลย เกิดจากอะไรและแก้ไงดี ? ความรู้เบื้องต้นของฐานข้อมูลที่โปรแกรมเมอร์ 90% ไม่รู้

    🤔 มันช้าลงได้ไง ?

    🔥 จุดเริ่มต้น

    ในวันแรกที่เราสร้าง database ขึ้นมาใหม่ๆ สดๆ ซิงๆ มีข้อมูลอยู่น้อยๆข้างใน ตามรูปด้านล่าง

    คราวนี้ถ้าเราส่งคำสั่งไปดึงข้อมูล โดยเอาเฉพาะคนที่อยู่จังหวัดกรุงเทพออกมาดิ๊ ตามโค้ดด้านล่าง

    เมื่อเราสั่งให้มันทำงาน database ก็จะวิ่งเข้าไปที่ตาราง User เพื่อค้นหาข้อมูลที่มีจังหวัดเป็นกรุงเทพกลับมาให้ ดังนั้นเราก็จะได้ผลลัพท์กลับมาประมาณนี้

    จุดที่น่าสนใจของเรื่องนี้ก็คือเจ้า database มันจะต้อง 🎯 ไล่ดูข้อมูลตั้งแต่ตัวแรกยันตัวสุดท้าย เพื่อลิสต์เอาเฉพาะข้อมูลที่ตรงตามเงื่อนไขส่งกลับมานั่นเอง ดังนั้นตัว database ของเราทุกคนจะทำงานได้ไวมากในช่วงแรกๆก็ไม่ใช่เรื่องแปลก เพราะ ข้อมูลมันน้อย ทำให้มันวิ่งไล่ตั้งแต่ไฟล์แรกยันไฟล์สุดท้ายได้ไวยังไงล่ะ

    🔥 ความหฤหรรษ์บังเกิด

    หลังจากที่เราใช้งานตัว database ไปได้ซักระยะ ตอบไม่ได้ว่านานแค่ไหน เพราะมันขึ้นอยู่กับ มันมีการเขียนไฟล์เยอะไหม? ไฟล์ใหญ่ใหม่? โค้ดที่เขียนกากหรือเปล่า? บลาๆ แต่ที่แน่ๆที่มันต้องเกิดกับทุกคนคือ ข้อมูลมันเยอะขึ้น ตามรูปด้านล่าง

    จากรูปด้านบน บางคนอาจจะดูเหมือนไม่เยอะ แต่ถ้าผมให้คุณลองหาข้อมูลเฉพาะที่อยู่ในกรุงเทพให้หน่อยดิ๊ ผมเชื่อว่าอย่างน้อยก็ใช้เวลา 3-5 วิ หรือมากกว่านั้นในการหาแน่นอน เพราะต้องกวาดตาไล่อ่านทีละอันชิมิ? ดังนั้น database ก็เช่นกัน เพราะไม่ว่าข้อมูลจะมากจะน้อย มันก็ต้องไล่ตรวจทีละข้อมูลตั้งแต่ตัวแรกยันตัวสุดท้ายอยู่ดียังไงล่ะ ตามรูปด้านล่างเลย

    ลองจินตนาการง่ายๆ ว่าตัว database ของเราก็เหมือนกับสมุดโทรศัพท์หน้าเหลืองเล่นหนาๆซักเล่ม แล้วก็มีคนมาสั่งให้เราไปหาเบอร์โทร โรงพยาบาลสรรพสิทธิประสงค์ โดยไม่บอกเลยว่าอยู่จังหวัดไหนดู นั่นแหละงานช้างเลย เพราะเราก็ต้องไปไล่อ่านทีละบรรทัดว่า อันไหนเป็นโรงพยาบาล และ ต้องเป็นโรงพยาบาลที่ชื่อ สรรพสิทธิประสงค์ ด้วยนะ

    ลองคิดดู ถ้ามันมีข้อมูลเป็นล้านๆข้อมูลล่ะ? ถ้าเราใส่เงื่อนไขที่ซับซ้อนมากขึ้นกว่านี้ล่ะ? ถ้าเราทำการ Join ตารางด้วยล่ะ? ถ้าเรา Transform data ด้วยล่ะ? (พอๆชักเยอะ) นี่แหละคือ หนึ่งในสาเหตุที่ทำให้ฐานข้อมูลของเรามันช้าลงไปเรื่อยๆ นั่นเอง

    🤔 มันจะเร็วขึ้นได้ไง ?

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

    จากปัญหาที่ว่ามาแนวคิดในการแก้ปัญหาก็คือ อย่าให้มันไปไล่หาแบบนั้นดิ ดังนั้น การทำ Database indexing ก็จะมาช่วยจัดระบบเบียบในการค้นหาใหม่ ทำให้มันเข้าถึงข้อมูลที่มันต้องการได้เร็วขึ้นนั่นเอง ดังนั้นถ้าเรามีข้อมูลเยอะๆประมาณรูปด้านล่างนี้

    เราก็แค่ทำการสร้าง indexing ให้กับตาราง Users ตามโค้ดด้านล่าง

    ซึ่งเจ้า indexing มันจะทำการแยกออกเป็นข้อมูลอีกชุดนึง โดยอ้างอิงจากข้อมูลที่มีในตาราง Users ตามรูปด้านล่าง

    โดยทั้งหมดนี้เจ้ากลุ่ม Indexing ก็จะจำว่าข้อมูลแต่ละกลุ่มมันอยู่ตรงไหนบ้าง ตามรูปด้านล่าง

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

    🤔 มีข้อเสียป่ะ ?

    ของทุกอย่างมันย่อมมีข้อดีและข้อเสียเสมอแหละ แม้ว่าตัว Database indexing มันจะช่วยทำให้เราวิ่งตรงไปหาข้อมูลได้เลย โดยอาศัยตาราง index เป็นเหมือนกับสารบัญก็ตาม แต่สิ่งที่แลกกับความสามารถนั้นก็คือ ตัวตาราง index เองนั่นแหละ

    ตัวตาราง index นี้มันจะช่วยให้ การค้นหา (Query) ทำได้รวดเร็วขึ้น เพราะไม่ต้องไล่มันทุกข้อมูลแล้ว แต่เราต้องแลกกับความช้าตอนทำ Insert, Update, Delete แทน เพราะว่า เวลาที่ข้อมูลในตารางที่ index มันดูแลอยู่มีการเปลี่ยนแปลง มันก็ต้องไปอัพเดทตาราง index ด้วยยังไงล่ะ และ มันต้องใช้พื้นที่ในการเก็บข้อมูลสำหรับ index เพิ่มด้วยเช่นกัน ตามรูปด้านล่าง

    Docker

    คอร์ส Docker หลักแสนที่ไม่ต้องจ่ายเงินเรียน 😘

    💖 เกริ่น

    คอร์สนี้ ดช.แมวน้ำ จะพาเพื่อนๆดำดิ่งกับเจ้าปลาวาฬสีน้ำเงินที่ชื่อว่า 🐳 Docker ตั้งแต่ความรู้พื้นฐานยันระดับ master กันไปเลย ซึ่งแมวน้ำเชื่อว่ามันจะเป็นคอร์สฟรีที่ให้ความรู้เท่าคอร์สที่ต้องจ่ายเงินหลักแสนแน่นอนฮ๊าฟ

    ความในใจ สาเหตุที่ตั้งคอร์สนี้ขึ้นมาเพราะ อยากส่งมอบความรู้ที่สมบูรณ์ให้ได้มากที่สุดแก่สังคมที่ผมรัก เพราะเรื่อง docker ในเบื้องลึกนั้นมันไม่ได้ง่ายแบบที่หลายๆคนเข้าใจ ดังนั้นถ้าใครเห็นเห็นจุดไหนอธิบายผิดไป หรืออยากเสริมตรงไหน โปรดอย่าได้รอช้าทำ Pull request หรือส่งข้อความหาผมได้ที่ลิงค์นี้เบยครัช

    เนื้อหาของ Docker ที่จะเอามาอธิบายมันเยอะม๊วกกกก แต่หลักๆที่อ่านจบผมรับรองว่าเราจะรู้จักและใช้งาน Docker ได้ สามารถจัดการ Microservices ด้วย Kubernetes เป็น และสามารถนำไปสร้าง Cluster บนผู้ให้บริการ Cloud Computing ได้แน่นอนครับ ... ซึ่งอย่างที่บอกแต่ละเรื่องมันสามารถแตกมาเป็นคอร์สของมันเองได้เลย ดังนั้นผมจะขอไล่ไต่ระดับไปทีละเรื่อง และบางเรื่องจะขอแยกออกมาเป็นคอร์สของมันอีกทีนะกั๊ฟ 🤠

    🤔 Docker คือไย? ทำไมนิยมจุง?

    😢 ปัญหา

    สมมุติว่าเราต้องเขียนเว็บซักตัวที่ต้องต่อ database เราต้องทำไงบ้าง? ... อย่างแรกเลยเราต้องติดตั้งโปรแกรมต่างๆในเครื่องเราก่อนชิมิ เช่นคนเขียน php ก็ต้องติดตั้ง Apache หรือ Java ก็ต้องติดตั้ง JDK, Node, .NET บลาๆแล้วแต่ว่าเราจะเขียนเว็บด้วยอะไร ส่วนตัว database เราก็ต้องไปติดตั้งมันเช่นกัน SQL, MySQL, MongoDB บลาๆขึ้นอยู่กับเราจะใช้ database ตัวไหน หรือพูดแบบรวบรัดง่ายๆคือ เราต้องเตรียม Environment ของเครื่องคอมเราให้พร้อมที่จะทำงานนั่นเอง จากที่ฟังดูก็เหมือนไม่มีอะไรยากเลยชิมิ? ตามรูปด้านล่าง

    แต่เวลาที่เราทำงานกันเป็นทีมจริงๆเราก็จะพบว่า ทุกคนในทีมก็ต้องเตรียม Environment แบบเดียวกัน จะได้ทำงานด้วยกันได้ ซึ่งเราก็จะพบกับความหฤหรรษ์ว่า บางคนใช้ Windows บางคนใช้ MacOS บางคนใช้ Linux และแม้แต่กระทั่ง Software Version ก็ยังเป็นคนละตัวกันอีกต่างหาก ตามรูปเบย

    ข้อควรระวัง บ่อยครั้งที่เราไม่สนใจว่าเครื่องแต่ละคนเป็นยังไงใครอยากใช้อะไรก็ใช้ไป มันจะทำให้เรามองไม่เห็นปัญหา เช่น การเขียนโค้ดแบบนี้มันทำงานได้กับ version นี้เท่านั้น พอไปทำงานที่เครื่องอื่นที่ไม่ได้ใช้ version นั้นๆ มันก็จะทำงานไม่ได้ ทำให้เกิดปัญหาที่หลายๆคนมักจะได้ยินว่า "ที่เครื่องผมมันทำงานได้นะ" งุยล่ะ

    สมมุติว่าเคลียกับคนในทีมจนได้ environments เหมือนกันทั้งทีมละ สุดท้ายเราก็ต้องเอาเว็บที่เราเขียนเสร็จ ไปทำงานที่เครื่องเซิฟเวอร์ซักตัว ซึ่งคำถามคือ ... เจ้าเซิฟเวอร์เครื่องนั้นมันถูกตั้ง Environments เหมือนกับของเราหรือเปล่า?

    สมมุติอีกว่าตั้งค่าต่างๆของเซิฟเวอร์ได้ไม่มีปัญหาละกัน ซึ่งอยู่มาวันหนึ่งคนเข้าชมเว็บเยอะขึ้นเราก็ต้องขยายเซิฟเวอร์เพื่อรองรับผู้ใช้ปริมาณมากๆ ... แล้วเครื่องเซิฟเวอร์ที่เราขยายเพิ่มขึ้นมา เราจะตั้งค่า Environment พวกนั้นยังไง? พวกมันจะคุยกันยังไง? Network ล่ะ? Infrastructure บลาๆ

    จากปัญหาที่ว่ามาก็น่าจะพอเห็นภาพแบบคร่าวๆแล้วล่ะว่า

    😄 วิธีแก้ปัญหา

    ซึ่งแน่นอนว่าปัญหาที่เราเจอ ขาเดฟทั่วโลกก็เจอปัญหานี้เช่นกัลล์ ซึ่งหนึ่งในการแก้ปัญหาแบบนี้ที่กำลังได้รับการนิยมอยู่ก็คือการใช้ 🐳 Docker นั่นเอง ซึ่งมันจะช่วยเราได้ยังไงลองย้อนกลับไปที่จุดเริ่มต้นกันนิสสส์นุง

    เมื่อเรา/ทีมจะเขียนอะไรก็ตาม เราก็จะตกลงกันให้เรียบร้อยว่าจะใช้ Environments เป็นแบบไหน แล้วเราก็จะสร้างสิ่งที่เรียกว่า 🖼️ Docker Image ขึ้นมา (ขออธิบายให้เข้าใจง่ายๆแบบนี้ก่อนละกันนะ)

    Docker Image คล้ายๆกับการที่เราทำ Backup Image ที่มันจะจำสถานะทุกอย่างที่เรากำหนดเอาไว้ เช่น ลงโปรแกรมอะไรไว้ เวอร์ชั่นเท่าไหร่ ของต่างๆถูกตั้งค่าเอาไว้ยังไง บลาๆ แล้วใครก็ตามที่เอา Image ไปใช้ก็จะได้สถานะ Environments ทุกอย่างตามที่ Image ถูกตั้งค่าไว้นั่นเอง

    หลังจากที่ได้ Docker Image ละถัดมา ทุกคนในทีมก็จะใช้ 🖼️ Docker Image ตัวนั้นอีกที โดยเจ้า Docker Image จะจัดการ Environments ทุกอย่างให้เป็นไปตามที่มันถูกระบุไว้ ตามรูปด้านล่างเบย

    และแน่นอนที่เซิฟเวอร์ก็สามารถเอาไปใช้ได้ และรวมถึงการทำ Server Scaling ก็เช่นกัน

    คำเตือน การทำ Scaling โดยใช้ Docker ไม่ได้ง่ายแบบในตัวอย่างนะจ๊ะ ซึ่งเนื้อหาทั้งหมดของการทำ Orchestration ทั้งหมดแมวน้ำจะขอแยกไปไว้ในคอร์ส Kubernetes นะกั๊ฟ เพราะในคอร์สนี้จะขอเน้นเรื่อง Docker เพียงอย่างเดียวไปก่อนนั่นเองฮ๊าฟ

    อาววล๊า เพียงเท่านี้ก็น่าจะพอเห็นภาพกันแล้วนะว่าทำไมเจ้าวาฬน้ำเงิน🐳 Docker ถึงได้เป็นที่นิยมกันจุง และน่าจะเพียงพอต่อการตัดสินใจว่าควรจะสละเวลาตีพุงดู Netflix ของเรามานั่งศึกษามันอ่ะป่าวละ ดังนั้นในบทความถัดไปของคอร์สนี้เดี๋ยวเราจะมาเริ่มลงรายละเอียดและลองใช้ Docker กันดูบ้างดีก่าฮั๊ฟ 😘

    คอร์สนี้กำลังค่อยๆเขียนอยู่ หากใครไม่อยากพลาดบทความถัดไปก็สามารถไปติดตามได้จากลิงค์นี้เบยครัช และบทความเรื่อง Docker & Microservices ก่อนหน้าแมวน้ำเคยเขียนไว้บางอันตั้งแต่สมัยยังไม่มีเว็บ Saladpuk เลยด้วยซ้ำ เลยคิดว่าจะลบเนื้อหาพวกนั้นออก เพราะคอร์สนี้จะพูดถึงเรื่องพวกนั้นอยู่แล้ว แต่หากใครสนใจก็สามารถไปดูได้จากลิงค์ด้านล่างนี้ครับ

    Lambda expression

    จากที่เคยพูดถึง Generic, Delegate, Action และ Func ไปเรียบร้อยแล้ว คราวนี้ตัว Lambda expression จะเป็นตัวจะช่วยให้เราทำงานกับเจ้าของพวกนั้นได้ง่ายขึ้นนั่นเอง ซึ่งโดยปรกติการเขียน lambda มันมี 2 แบบครับคือ

    1.เขียนแบบเต็ม (Statement lambda)

    การเขียนแบบเต็มก็คือมีการสร้าง body ของมันออกมา แล้วข้างใน body นั้นเราจะให้มัน statements อยู่กี่ตัวก็ได้ ตามโค้ดด้านล่าง

    2.เขียนแบบย่อ (Expression lambda)

    การเขียนแบบย่อคือมันจะต้องไม่มี body และมีได้เพียง statement เดียวเท่านั้น ตามโค้ดด้านล่าง

    หลังจากที่ได้เห็นโค้ดตัวอย่างทั้ง 2 แบบไป เราจะเห็นสัญลักษณ์แปลกๆนั่นก็คือเจ้า => นั่นเองซึ่งเจ้าตัวนี้แหละคือการบอกว่า มันคือ Lambda Expression นะ โดยการใช้งาน lambda จะต้องประกอบด้วยของ 2 อย่างคือ

    • Input parameters - เป็นตัวที่เอาไว้กำหนดว่า ถ้ามีการเรียกใช้งาน lambda มันจะทำการ map parameter แต่ละตัวเข้ามาในชื่ออะไร

    • Expression - เป็นตัวที่เอาไว้บอกว่า ถ้ามีการเรียกใช้งาน lambda แล้ว lambda จะต้องทำงานยังไง

    จากที่ว่ามาอาจจะ งงๆ ว่ามันทำงานยังไง ดังนั้นเราไปดูตัวอย่างการใช้งานจริงกันเลยดีกว่าว่า ถ้าเราอยากได้ method ที่ทำงานเหมือนโค้ดด้านล่างนี้ เราจะเขียนโดยใช้ Lambda ยังไงดี

    🔥 Action

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

    เขียนแบบเต็ม

    เขียนแบบย่อ

    แบบมี parameter ตัวเดียว

    แล้วถ้า method ตัวนั้นมี parameter อยู่ด้วยล่ะ สมมุติว่าเป็นแบบนี้

    เราก็สามารถกำหนด parameter ในส่วนของ (input-parameters) ได้ยังไงล่ะ ตามนี้เลย

    โดยเจ้าโค้ดด้านบนมันจะทำการ map เองว่า int ที่รับเข้ามา มันจะไปกำหนดค่าให้กับตัวแปร a นั่นเอง

    แบบมี parameters หลายตัว

    คราวนี้ถ้ามันมี parameters หลายๆตัวบ้างล่ะ? ประมาณนี้

    เราก็สามารถเขียน lambda ออกมาเป็นแบบนี้ได้

    🔥 Func

    คราวนี้ลองมาดู method แบบที่มี return type ดูบ้างว่ามันจะเป็นยังไง โดยสมมุติว่าเราต้องการแปลงเจ้าโค้ดตัวนี้ให้เป็น func บ้าง

    เขียนแบบเต็ม

    เขียนแบบย่อ

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

    🔥 Delegate

    ถึงคราวของเจ้า delegate ดูบ้างละ ซึ่งปรกติเราไม่น่าจะเจอโค้ดแบบนี้แล้วนะ เพราะมันเก่ามากละ โดยสมมุติว่าเรามี Method ง่ายๆอยู่ตัวนึง ประมาณนี้ละกัน

    แล้วเราอยากทำ delegate ไปหา method ตัวนี้ โดยปรกติเราก็จะเขียนแบบนี้ชิมิ

    จากโค้ดด้านบนทั้งหมด จะเห็นว่ามันเสียเวลาที่เราต้องไปสร้าง SimpleMethod ก่อน แล้วค่อยเอามากำหนดค่าให้กับเจ้า delegate ดังนั้นในรอบนี้เราก็จะเอา Lambda มาช่วยเพื่อให้โค้ดของเรากระชับขึ้น ตามโค้ดด้านล่างนี้

    เขียนแบบเต็ม

    เขียนแบบย่อ

    🎯 บทสรุป

    โดยรวมๆการใช้ Lambda expression ก็จะเป็นช่องทางให้เราสามารถเขียนโค้ดได้ง่ายและไม่เวิ่นเว้อจนเกินไป โดยการสร้าง anonymous method ผ่านการใช้ lambda นั่นเอง ซึ่งจริงๆมันมีวิธีการใช้อีกเยอะม๊วก เช่น การทำงานกับ async, await, การทำงานกับ tuples ซึ่งผมจะยังไม่ขออธิบายไว้ในตรงนี้ละกันนะ เดี๋ยวมาอัพเดทเป็นเรื่อง advance ของ Lambda expression เอาดีกว่า

    แนะนำให้อ่าน เรื่องนี้สามารถไปศึกษาเพิ่มเติมได้จากต้นทางตามลิงค์ด้านล่างนี้เบย

    Challenges

    ปริศนาลับสมอง

    🤠 เนื่องจากมีผองเพื่อนแมวน้ำเสนอไอเดียให้ เอาโจทย์สนุกๆมาวิเคราะห์แล้วพาเขียนโค้ดให้ดูหน่อย ซึ่ง ดช.แมวน้ำ คิดว่าก็น่าสนุกดีเหมือนกัน ดังนั้นถ้ากระผมเจอโจทย์อะไรสนุกๆก็จะเอามาลงไว้ในหน้านี้ พร้อมอธิบายไปทีละขั้นตอนนั่นเองฮ๊าฟ

    หากใครมีโจทย์ที่สนุกๆก็เอามาแชร์กันได้ที่ Saladpuk Fanclub ได้นะครับ 😍

    🧭 รายการโจทย์

    ว่างๆเดี๋ยวเอามาเขียนต่อตรงนี้เรื่อยๆแหละ

    3.ชนิดของข้อมูล

    💬 ก่อนที่เราจะเขียนโค้ดตัวแรกจริงๆกัน เราต้องรู้จักกับสิ่งที่เรียกว่า ชนิดของข้อมูล หรือ data type เสียก่อน ซึ่งข้อมูลแต่ละชนิดก็จะเหมาะกับงานแต่ละแบบ

    ถ้าเราไม่เข้าใจจุดนี้มันก็เหมือนกับเราเอาค้อนไปเลื่อยไม้นั่นเอง เพราะเราไม่เข้าใจการทำงานพื้นฐานของข้อมูลแต่ละรูปแบบ ซึ่งจุดนี้ผมเจอมาเยอะมากเลยแม้กระทั่งนักพัฒนาที่เขียนโปรแกรมมาเป็น 10 ปีบางคนก็ไม่ได้เข้าใจในจุดนนี้อย่างถ่องแท้ ทำให้เวลาเจอข้อผิดพลาดก็จะพากัน งงๆ ว่าเกิดอะไรขึ้น

    🎯 สรุปสั้นๆ

    👨‍🚀 ประเภทของข้อมูลหลัก

    ในโลกของการเขียนโปรแกรม เราควรต้องรู้จักประเภทข้อมูล 4 ตัวแรกมีตามนี้เบย

    Google ม้า 25 ตัว

    โจทย์สอบสัมภาษณ์เข้า Google

    🥳 โจทย์

    โจทย์ข้อนี้เห็นว่า Google ใช้สอบสัมภาษณ์ โดยเข้าให้โจทย์เรามาประมาณนี้

    Interview Question There are 25 mechanical horses and a single racetrack. Each horse completes the track in a pre-programmed time, and the horses all have different finishing times, unknown to you. You can race 5 horses at a time. After a race is over, you get a printout with the order the horses finished, but not the finishing times of the horses. What is the minimum number of races you need to identify the fastest 3 horses?

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

    2019-12

    ✏️ ประวัติการอัพเดท

    สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

    try
    {
       // โค้ดที่มีความเสี่ยงว่าอาจจะเกิด error ขึ้นได้
    }
    catch (Exception e)
    {
       // ถ้าเกิด error ขึ้นภายในบรรทัดที่ 2~4 มันจะกระโดดมาทำภายใน block นี้
    }
    try
    {
       // โค้ดที่มีความเสี่ยงว่าอาจจะเกิด error ขึ้นได้
    }
    catch (DivideByZeroException e)
    {
       // ถ้าเกิด error ขึ้นภายในบรรทัดที่ 2~4 
       // และเป็น error จากการที่ตัวหารเป็น 0 มันจะกระโดดมาทำภายใน block นี้
    }
    catch (NullReferenceException e)
    {
       // ถ้าเกิด error ขึ้นภายในบรรทัดที่ 2~4
       // และเป็น error จากการที่เราใช้ตัวแปรที่เป็น null มันจะกระโดดมาทำภายใน block นี้
    }
    catch (Exception e)
    {
       // ถ้าเกิด error ขึ้นภายในบรรทัดที่ 2~4
       // และ error ที่เกิดขึ้นไม่ใช่เกิดจาก ตัวหารเป็น 0 หรือ เรียกใช้ตัวแปรที่เป็น null
       // มันจะกระโดดมาทำภายใน block นี้
    }
    try
    {
       // โค้ดที่มีความเสี่ยงว่าอาจจะเกิด error ขึ้นได้
    }
    catch (Exception e)
    {
       // ถ้าเกิด error ขึ้นภายในบรรทัดที่ 2~4 มันจะกระโดดมาทำภายใน block นี้
    }
    finally
    {
       // เมื่อทำ try หรือ catch เสร็จ มันจะกระโดดมาทำภายใน block นี้เสมอ
    }
    try
    {
       // โค้ดที่มีความเสี่ยงว่าอาจจะเกิด error ขึ้นได้
    }
    catch (Exception e)
    {
       throw e;
    }
    public void Greeting(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new ArgumentNullException();
        }
    
        Console.Write("Greeting " + name);
    }
    throw new ArgumentNullException("name", "กรุณากำหนดชื่อเข้ามาด้วย");
    public class MyCustomException : Exception
    {
    }
    public void Divide(double dividend, double divisor)
    {
       if(divisor == 0)
       {
          Console.WriteLine("ตัวหารไม่สามารถเป็นศูนย์ได้นะ!")
       }
       else
       {
          Console.WriteLine(dividend / divisor);
       }
    }
    namespace ProjectName.Models
    {
        public class Teacher
        {
            public int Id { get; set; }
            public string Name { get; set; }
        }
        public class Student
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public Teacher TeacherAssistance { get; set; }
        }
    }
    
    namespace ProjectName.DAC
    {
        public interface SQLConnector
        {
            void Connect();
            void Disconnect();
        }
        public interface IMongoDbConnector
        {
            void Connect();
            void Disconnect();
        }
    }
    using System;
    using ProjectName.Models;
    
    namespace DemoConsoleApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Teacher t01 = new Teacher();
            }
        }
    }
    using sys = System;
    
    namespace DemoConsoleApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                sys.Console.WriteLine("Hello World!");
            }
        }
    }
    string txt = "Hello world";
    Console.WriteLine(txt[0]); // บรรทัดนี้จะได้ H
    Environment.NewLine // ใช้ตัวนี้แทน "\n" นะจ๊ะ
    string name = "Au";
    int age = 18;
    string txt = string.Format("Hello, {0}, Age: {1}", name, age);
    string name = "Au";
    int age = 18;
    string txt = $"Hello, {name}, Age: {age}";
    string txt = "Hello world!";
    string result = txt.Substring(0, 4);  // result มีค่าเป็น Hell
    string txt = "Hello world!";
    int result = txt.IndexOf("world");  // result มีค่าเป็น 6
    string txt = "Hello world!";
    string result = txt.Replace("world", "Cat");  // result มีค่าเป็น "Hello Cat"
    public class MyClass
    {
       private string name;
    
       public string Name
       {
          get { return name; }
          set { name = value; }
       }
    }
    public class MyClass
    {
       public string Name { get; set; }
    }
    public class MyClass
    {
       private bool isMale;
    
       private string name;
    
       public string Name
       {
          get
          {
             var title = isMale? "Mr." : "Ms.";
             return title + name;
          }
          set
          {
             name = value.ToLower();
          }
       }
    }
    public class MyClass
    {
       public string Name { get; private set; }
    }
    public class MyClass
    {
       public string Name { get; set; }
       public int Age { get; }
       public string Address { set; }
    }
    if( เงื่อนไข )
    {
      // ทำใน block นี้ถ้าเงื่อนไขเป็นจริง
    }
    if( เงื่อนไข )
    {
      // ทำใน block นี้ถ้าเงื่อนไขเป็นจริง
    }
    else
    {
      // ทำใน block นี้ถ้าเงื่อนไขเป็นเท็จ
    }
    if( เงื่อนไขที่ 1 )
    {
      // ทำใน block นี้ถ้าเงื่อนไขที่ 1 เป็นจริง
    }
    else if( เงื่อนไขที่ 2 )
    {
      // ทำใน block นี้ถ้าเงื่อนไขที่ 2 เป็นจริง (และเงื่อนไขด้านบนทุกตัวเป็นเท็จ)
    }
    else
    {
      // ทำใน block นี้ถ้าเงื่อนไขทุกตัวเป็นเท็จ
    }
    if( เงื่อนไข 1 )
    {
      if( เงื่อนไข 2)
      {
        // ทำใน block นี้ถ้าเงื่อนไข 1 และ 2 เป็นจริง
      }
      else
      {
        // ทำใน block นี้ถ้าเงื่อนไข 1 เป็นจริงแต่ 2 เป็นเท็จ
      }
    }
    string message = เงื่อนไข ? "กรณีเงื่อนเป็นจริงจะใช้ค่านี้" : "กรณีเงื่อนไขเป็นเท็จจะใช้ค่านี้";
    👶 SOLID Design Principles
    Single-Responsibility Principle (SRP)

    🤖 มาลองหัดสร้าง ChatBot กัน

    📝 สิ่งที่คนเขียนโค้ดเข้าใจผิดกันบ่อยๆ (รีบแก้ซะ)

  • 📝 ทำไมโปรเจคเราถึงช้า + แก้ไง ?

  • ☁️ มารู้จักกับคลาว์กันก่อนไหม
    ☁️ มาลองใช้คลาว์กันดูว่าทำไรได้บ้าง
    ☁️ ทำที่ฝากไฟล์ไว้บทคลาว์
    🤖 มารู้จักกับ AI กันก่อนว่ามันคืออะไร
    🤖 หัดใช้งาน AI สำเร็จรูป
    🤖 อยากเขียน AI ของตัวเองต้องทำไง ?
    🤖 มาหัดเขียน AI เองหมดตั้งแต่ต้นกัน
    📝 เขียนโค้ดด้วยภาษา C# ขั้นพื้นฐานยันขั้นสูง
    📝 การเขียนโค้ดแบบ Object-Oriented Programming (OOP)
    📝 การใช้ Git ขั้นพื้นฐาน
    📝 การทำ Clean Code
    บทสรุปฐานข้อมูล
    หัวใจที่สำคัญที่สุดของฐานข้อมูล
    Test-Driven Development (TDD)
    ตัวอย่างการทำ Test First
    โค้ดเน่าคืออะไร?
    การเขียนแผนภาพ UML ขั้นพื้นฐาน
    หลักในการออกแบบที่ต้องรู้ SOLID Design
    การแก้ปัญหาด้วย Design Patterns
    กาวางแผนรับมือทำโปรเจคด้วย Agile
    มารู้จักกับ DevOps กัน
    มาหัดใช้งาน Azure DevOps เพื่อลดการเสียเวลากันเถอะ
    Microservices พื้นฐาน
    มาหัดเขียน Microservics กันด้วย Service Fabric
    Security พื้นฐาน
    🔐 Cloud Playground
    มารู้จักและหัดเขียน Blockchain กัน
    มาหัดใช้ Docker กันเถอะ
    ⛩️ Web API

    📐 หลักในการออกแบบที่ต้องรู้ SOLID Design

  • 📐 การแก้ปัญหาด้วย Design Patterns

  • Facebook Blog: Mr.Saladpuk
    💖Abstraction
    💖Encapsulation
    🏆Abstraction & Encapsulation
    💖Inheritance
    💖Polymorphism
    🏆Inheritance & Polymorphism
    📝ลองเขียน OOP ดูดิ๊
    👑OOP + Power of Design
    🥰เทคนิคในการออกแบบ
    Test-Driven Development (TDD)
    ตัวอย่างการทำ Test First
    🐴Google ม้า 25 ตัว
    🌉Amazon เสา 2 ต้น
    🥇ทองเก๊
    💊ยาต้านโควิด
    🎩CP หมวก 5 ใบ
    🧓Einstein's Riddle 01
    23/12/2019
    • อัพเดท 👦 Design Patterns เรื่อง 🏗️ Builder Pattern

    16/12/2019

    • อัพเดท 👦 Design Patterns เรื่อง ☝️ Singleton Pattern

    15/12/2019

    • อัพเดท 👦 Design Patterns เรื่อง 🏭 Abstract Factory Pattern

    13/12/2019

    • แก้ไขบทความ 🏭 Factory Method Pattern โดย เพิ่มเหตุผล, ข้อดี/ข้อเสีย พร้อมอธิบายว่ามันช่วยอะไรเราบ้าง

    12/12/2019

    • อัพเดท 👦 Design Patterns เรื่อง 🏭 Factory Method Pattern

    11/12/2019

    • อัพเดท 👦 Design Patterns เรื่อง 🤰 Creational Patterns

    10/12/2019

    • เขียนคอร์ส 👦 Design Patterns ฉบับไม่เมากาว

    03/12/2019

    • อัพเดท 👶 Machine Learning Studio เรื่อง ลองเรียกใช้ AI ของเรากัน

    01/12/2019

    • อัพเดท 👶 Object-Oriented Programming เรื่อง 🥰 เทคนิคในการออกแบบ

    Facebook Blog: Mr.Saladpuk
    Microsoft document - Lambda expression
    (input-parameters) => { <sequence-of-statements> }
    (input-parameters) => expression
    static void SimpleMethod()
    {
        Console.WriteLine("SimpleMethod");
    }
    Action simpleMethod = () =>
    {
        Console.WriteLine("SimpleMethod");
    };
    Action simpleMethod = () => Console.WriteLine("SimpleMethod");
    static void MethodWithParameter(int a)
    {
        Console.WriteLine(a);
    }
    Action<int> simpleMethod = (a) =>
    {
        Console.WriteLine(a);
    };
    static void MethodWithParameter(int a, string b)
    {
        Console.WriteLine(a);
        Console.WriteLine(b);
    }
    Action<int, string> simpleMethod = (a, b) =>
    {
        Console.WriteLine(a);
        Console.WriteLine(b);
    };
    static int SimpleMethod()
    {
        return 99;
    }
    Func<int> simpleMethod = () =>
    {
        return 99;
    };
    Func<int> simpleMethod = () => 99;
    static void SimpleMethod()
    {
        Console.WriteLine("Saladpuk");
    }
    // สร้าง delegate ออกมาก่อน
    delegate void DEMO_DELEGATE();
    
    // ตอนเอา delegate ไปใช้งาน
    DEMO_DELEGATE handler = Awesome;
    DEMO_DELEGATE handler = () =>
    {
        Console.WriteLine("Saladpuk");
    };
    DEMO_DELEGATE handler = () => Console.WriteLine("Saladpuk");

    10 * 5

    /

    นำค่า 2 ตัวที่อยู่ใกล้กันมา หาร กัน

    10 / 5

    Operator

    ความหมาย

    ตัวอย่าง

    =

    กำหนดค่าให้กับตัวแปรที่อยู่ด้านซ้ายมือ

    int money = 100;

    +

    นำค่า 2 ตัวที่อยู่ใกล้กันมา บวก กัน

    10 + 5

    -

    นำค่า 2 ตัวที่อยู่ใกล้กันมา ลบ กัน

    10 - 5

    *

    Operator

    ความหมาย

    +=

    นำค่าทางขวามือไปบวกกับด้านซ้ายมือ แล้วกำหนดให้ตัวแปรด้านซ้ายมือเป็นค่านั้นๆเลย

    -=

    นำค่าทางขวามือไปลบกับด้านซ้ายมือ แล้วกำหนดให้ตัวแปรด้านซ้ายมือเป็นค่านั้นๆเลย

    *=

    นำค่าทางขวามือไปคูณกับด้านซ้ายมือ แล้วกำหนดให้ตัวแปรด้านซ้ายมือเป็นค่านั้นๆเลย

    /=

    นำค่าทางขวามือไปหารกับด้านซ้ายมือ แล้วกำหนดให้ตัวแปรด้านซ้ายมือเป็นค่านั้นๆเลย

    ลำดับที่

    เครื่องหมาย

    1

    ( วงเล็บ )

    2

    ++ หรือ -- (prefix)

    3

    คูณ หาร

    4

    บวก ลบ

    5

    ++ หรือ -- (postfix)

    นำค่า 2 ตัวที่อยู่ใกล้กันมา คูณ กัน

    Paging

  • Calculation value

  • Lean computations

  • Design Document database (MongoDB) เดี๋ยวเปิดสอนการทำ MongoDb เป็นอีกคอร์สเลยละกัน

  • คิดไรได้เดี๋ยวเอามาใส่ต่อ

  • ความสัมพันธ์

    การออกแบบ

    1:1

    เลือก Primary Key ของตารางไหนก็ได้ ไปใส่อีกตารางหนึ่ง เพื่อใช้เป็น Foreign Key

    1:Many

    เลือก Primary Key ของตารางที่เป็น 1 ไปใส่ในตารางที่เป็น Many เพื่อนำไปเป็น Foreign Key

    Many:Many

    สร้างตารางใหม่ขึ้นมา โดยนำ Primary Key ของทั้ง 2 ตารางเข้าไปในตารางใหม่

    เพื่อนำไปเป็น Foreign Key และแนะนำให้สร้าง Primary Key ของตัวเองขึ้นมาด้วย

    Mr.Saladpuk
    เก็บรูปในฐานข้อมูล
    Database indexing
    การลบข้อมูล
  • 👶 Azure Service Fabric

  • https://www.facebook.com/mr.saladpuk
    📦Docker Containers
    Saladpuk Fanclub
    👶 Docker ขั้นพื้นฐาน
    Docker Image ไม่ใช่ไฟล์รูปนะ 🤣
    ช่องทางสนับสนุนค่าอาหารแมวน้ำกั๊ฟ 😘
    👶 Microservices พื้นฐาน

    true หรือ false

    Data type

    ใช้กับ

    ตัวอย่างข้อมูล

    int

    ตัวเลขจำนวนเต็ม

    1000

    double

    ตัวเลขจำนวนเต็ม หรือ ทศนิยม

    32.76

    string

    ข้อความ และจะต้องอยู่ภายใต้ double quote

    "Hello"

    bool

    ข้อมูลที่เป็น จริง หรือ เท็จ เท่านั้น

    เราจะใช้สนามแข่งน้อยที่สุดกี่ครั้ง เพื่อจะหาม้าที่วิ่งเร็วที่สุด 3 ตัวแรกได้

    จากตรงนี้ก็ลองคิดกันต่อดูนะว่าคำตอบคือเท่าไหร่ ? ส่วนใครที่คิดได้/ขี้เกียจคิดละก็เชิญอ่านเฉลยด้านล่างได้เบย 😘

    คนส่วนใหญ่จะตอบผิดว่า 6 ครั้ง เพราะยังคิด scenarios ได้ไม่ครบนั่นเองขอรับ

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของ 🧠 Challenges ที่จะคอยรวบรวมโจทย์ที่น่าสนุกคิดเพลินๆ หากใครสนใจอยากดูว่ามีโจทย์อะไรบ้างก็อัญเชิญกดที่ชื่อสีฟ้าๆไปเสพต่อได้เบย ส่วนใครที่คิดว่ามีโจทย์น่าสนใจก็สามารถส่งมาได้ที่ Saladpuk Fanclub นะกั๊ฟ 😘

    🤠 วิธีคิด

    สิ่งแรกที่เรารู้คือมัน มีม้า 25 ตัว ตามรูปด้านล่าง

    ซึ่งเราก็รู้นะว่า ม้าแต่ละตัววิ่งเร็วไม่เท่ากัน ดังนั้นมันต้องมี 🐎 ม้าที่วิ่งได้ไวที่สุด อยู่แต่แค่เราไม่รู้ว่ามันเป็นตัวไหนเฉยๆ ดังนั้นถ้าเราเอาทุกตัวมาแข่งกัน เราก็จะหาตัวที่วิ่งได้เร็วที่สุดได้แน่นอน

    🔥 จัดกลุ่ม

    สิ่งถัดมาที่เรารู้คือ สนามแข่งรองรับม้าได้รอบละ 5 ตัว ดังนั้นถ้าเราจะเอาม้าทุกตัวมาวิ่งแข่งกัน เราจะต้องจัดกลุ่มอย่างน้อย 5 กลุ่ม (A, B, C, D, E) ตามรูปด้านล่าง

    จะจัดแนวตั้งแนวนอนก็ทำไปเถอะ

    🔥 เรียงลำดับในกลุ่ม

    ม้าในแต่ละกลุ่มตอนนี้มันอยู่มั่วซั่วไปหมด ดังนั้นในแต่ละกลุ่มเราต้องจับมัน เรียงลำดับจากความเร็วมากไปหาน้อย โดยการให้แต่ละกลุ่มวิ่งแข่งกันในสนามซะ ซึ่งก็จะได้ผลตามรูปด้านล่าง

    😎 เนื่องจากเราเอาทั้ง 5 กลุ่มไปวิ่งในสนาม ดังนั้นตอนนี้เราใช้สนามไปทั้งหมด 5 ครั้งละ

    🔥 ม้าที่ไวที่สุด

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

    ม้าสีแดงคือตัวที่วิ่งได้เร็วที่สุด

    😎 ในตอนนี้หาม้าที่วิ่งได้เร็วที่สุดได้แล้ว โดยที่เราใช้สนามไปทั้งหมด 6 ครั้ง ซึ่งเราก็จะเหลือแค่หาม้าที่วิ่งได้ไวที่สุดเป็นอันดับ 2 กับ 3 แค่นั้นเอง

    🔥 ม้าเบอร์ 2 กับ 3

    👻 กับดัก

    ตรงจุดนี้มีหลายคนติดกับดักเยอะม๊วกกกกก เพราะคิดว่าม้าที่เร็วเป็นอันดับ 2-3 จะอยู่ในแถวที่ 1 ด้วยชิมิ ... ซึ่งมันจะถูกในบางกรณี แต่กรณีส่วนใหญ่จะไม่ถูกครับ ตามรูปด้านล่าง

    ม้าเบอร์ 2 กับ 3 อาจจะไม่ได้อยู่ตรงนั้นเสมอไป

    เพราะว่าเราไม่มีทางรู้เลยว่าม้าจะถูกจัดกลุ่มแบบไหน ดังนั้นจะเกิดอะไรขึ้นถ้าม้าที่วิ่งเร็วเบอร์ 1,2,3 ดันถูกจัดไว้ในกลุ่มเดียวกันตั้งแต่แรกล่ะ? ตามรูปด้านล่าง

    🤓 ดังนั้นหากได้คำตอบว่า 6 ครั้งก็จะยังถือว่าตอบไม่ถูก เพราะมันยังไม่ครบทุกเคสที่อาจะเกิดขึ้นได้นั่นเอง

    🤠 วิธีคิดในการตัดตัวเลือก

    ถ้าเราเจอเหตุการณ์ที่มีตัวเลือกเยอะเต็มไปหมด สิ่งที่เราควรจะทำคือ หาว่าตัวเลือกไหนตัดทิ้งได้ โดยการค่อยๆวิเคราะห์จากทีละเรื่อง

    หาจุดที่ม้าเบอร์ 2 อยู่ได้

    ถ้าเรามานั่งคิดดูดีๆก็จะพบว่า ม้าเบอร์ 2 มันต้องไม่มีทางวิ่งได้ตำกว่าตำแหน่งที่ 2 แน่นอน ดังนั้นจุดที่มันควรจะอยู่ได้ก็จะมีแค่ตามในรูปด้านล่าง

    ตำแหน่ง 2-2 ไม่มีทางอยู่ได้ เพราะม้าเบอร์ 1 โดนล๊อคที่ไว้แล้ว

    หาจุดที่ม้าเบอร์ 3 อยู่ได้

    เช่นเดียวกัน ม้าเบอร์ 3 มันต้องไม่มีทางวิ่งได้ต่ำกว่าตำแหน่งที่ 3 แน่นอน ดังนั้นจุดที่มันควรจะอยู่ได้ก็คือตามรูปด้านล่าง

    สาเหตุที่มันอาจไปอยู่ตำแหน่งที่ 2 ได้ก็จะเป็นกรณีที่ม้าเบอร์ 2 ไปอยู่กลุ่มเดียวกันกับม้าเบอร์ 1 นั่นเอง

    จากที่ว่ามาเราก็จะพบว่า ตำแหน่งของม้าเบอร์ 2 กับ 3นั่นจะต้องอยู่ภายในสีเขียวเท่านั้น ดังนั้นเราก็แค่เอาม้าทุกตัวที่อยู่ในสีเขียวมาวิ่งแข่งกัน เราก็จะหาม้าที่วิ่งไวที่สุดเบอร์ 2 กับ 3 ได้แล้วนั่นเอง ซึ่งตำแหน่งสีเขียวมี 5 อันพอดีกับลูกวิ่งเลย

    😎 จากทั้งหมดเราก็สามารถหาม้าที่วิ่งไว้ที่สุด 3 อันดับแรกได้แล้ว โดยที่เราใช้สนามไปทั้งหมด 7 ครั้งครัช

    🤔 ต่ำกว่า 7 ได้มะ?

    หลายคนเมื่อเห็นคำตอบก็อาจสงสัยว่า 7 ครั้งคือคำตอบที่น้อยที่สุดแล้วหรือเปล่า ดังนั้นเราลองมาคิดกันต่ออีกนิสนุงกันครัช 😁

    🐴 ม้าทุกตัวต้องถูกจับวิ่ง

    อย่างแรกเลยเราไม่รู้ว่าม้าทั้ง 25 ตัวมันวิ่งเร็วเท่าไหร่ และตัวไหนวิ่งเร็ววิ่งช้าก็ไม่มีทางรู้ได้ถ้าไม่เอามันไปวิ่งในสนาม ซึ่ง เราต้องเอาม้าทุกตัวลงวิ่งโดยห้ามละเว้นซักตัว เพราะถ้าตัวที่ถูกละเว้นมันเป็นตัวที่วิ่งเร็วเบอร์ 1-3 ขึ้นมาเราก็จะได้คำตอบผิด

    🦸‍♀️ จำนวนรอบขั้นต่ำ

    เนื่องจากเราต้องเอาม้าทั้ง 25 ตัวไปวิ่ง ซึ่งสนามรับได้รอบละ 5 ตัว ดังนั้น ขั้นต่ำสุดที่จะเอาม้าไปวิ่งได้หมดคือ 5 รอบ (5x5=25)

    👩‍🔬 Cross check

    แม้เราจะเอาม้าไปวิ่งครบแล้วก็จริง แต่จะเห็นว่ามันแยกกันเป็นกลุ่มๆไม่เกี่ยวข้องกันเลย ดังนั้นเราจะต้อง ตรวจข้ามกลุ่มกัน ซึ่งเราจะส่งม้าตรวจข้ามกลุ่มมั่วๆมันก็จะเสียการใช้สนามไปเปล่าๆ ดังนั้นเราควรจะเลือกตัวที่ส่งผลดีที่สุดไป นั่นคือ เลือกม้าที่เร็วสุดของแต่ละกลุ่มมาแข่งกัน นั่นเอง

    ซึ่งจากการใช้สนามครั้งที่ 6 นี้เราจะได้คำตอบของสมการตัวแรกคือม้าที่เร็วสุด จะต้องอยู่ใน Cross Check นั้นแน่นอน แต่สำหรับม้าเบอร์ 2 กับ 3 เราไม่สามารถหาความสัมพันธ์ใดๆได้เลยว่ามันจะอยู่ในกลุ่ม Cross check หรือเปล่า ดังนั้น 6 เลยไม่มีทางที่จะเป็นคำตอบได้นั่นเองขอรับ

    6 มีโอกาสตอบถูกเหมือนกันแต่ไม่ใช่สำหรับทุกกรณี ถ้าถามว่าเรารับกันได้ งั้นผมก็ขอบอกว่า ผมก็สุ่มมั่วๆมา 3 ตัวก็มีโอกาสถูกเหมือนกันนิครับ 😅

    🤠 หากใครมีวิธีคิดที่ได้ผลลัพท์เร็วกว่านี้ก็สามารถส่งคำตอบมาได้เลยนะ จะได้ช่วยกันผลักดันวิธีคิดใหม่ๆกันฮ๊าฟ

    🎯 ข้อคิดที่ได้

    • Scenarios นั้นสำคัญมาก เพราะถ้าเราไม่คิดมันไว้ก่อนเราก็จะได้คำตอบที่ต่ำกว่า 7 ครั้ง

    • เวลาที่เราเจอโจทย์ อย่ากระโดดไปเขียนโค้ดเลย ไม่งั้น solution / performance ที่ได้มันจะค่อนข้างห่วย ให้คิดให้ดีก่อนว่า จะแก้ปัญหาเรื่องนั้นๆยังไงโดยออกแรงให้น้อยที่สุด ดังนั้น การวางแผน คือ 💖 หัวใจหลักในการทำซอฟต์แวร์

    • เวลาที่เราเจอโจทย์อะไรก็ตาม เราไม่จำเป็นต้องหาวิธีที่ดีที่สุดในการแก้ปัญหาก็ได้ ขอแค่ให้เราได้แนวทางที่จะแก้ไขโจทย์นั้นๆได้ก่อน แล้วเมื่อเราเข้าใจภาพรวมทั้งหมดแล้วค่อยกลับมาหาวิธี Optimize ให้มันดีขึ้นภายหลังก็ได้ กฎ 80:20

    Blueprint
    👶 บทสรุปฐานข้อมูล
    👦 Bottlenecks of Software
    หัวใจที่สำคัญที่สุดของฐานข้อมูล
    ตาราง Users
    ตาราง index ที่จะมาเป็นปัญหาใหม่
    ถ้ามีการแก้ไข้ข้อมูลมีการจัด index อยู่ มันจะต้องมาอัพเดท index ด้วยเสมอ

    Algorithm Big-O

    ลองดูดิ๊การบวกเลขมันง่ายจิงป่ะ ? 🤪

    จากบทความก่อนหน้าหลายคนน่าจะเริ่มเห็นความสำคัญของ Algorithm กันเพิ่มขึ้นละ ดังนั้นในบทความนี้ ดช.แมวน้ำ จะยกตัวอย่าง การบวกเลข ให้เห็นว่ามันแตกประเด็นไปถึงเรื่องการทำ Security ได้ยังงุยกัน (ขอแบ่งเป็น 2 บทนะ)

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของบทความ 👶 Data Structure & Algorithm หากเพื่อนๆสนใจอยากรู้ว่าเจ้า Algorithm มันคืออะไร คุ้มค่าเวลาชีวิตที่เราจะอ่านไหม ก็สามารถศึกษาได้จากบทความหลักได้เลยโดยการคลิกที่ชื่อบทความสีฟ้าๆนั่นแหละ

    🥱 บวกเลข ป.3

    สมมุติว่าแมวน้ำเขียนโค้ดตามด้านล่าง

    คำถามคือ ตัวแปร c มีค่าเป็นเท่าหร่ายยยยจ๊ะ ?

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

    ผลลัพท์ 0.30000000000000004 😳 เฮ้ยมันเกิดบร้าไรขึ้นทำไมมันถึงมีติ่งแปลกๆงอกขึ้นมาดั๊ยยยยย!! ลองไปเขียนดูนะ C#, Java, Javascript, Python บลาๆ 🤪

    สาเหตุที่คอมมันได้ผลลัพท์แบบนั้นออกมาก็เพราะว่า Data Structure & Algorithm ของตัวเลขมันเป็นแบบนั้นยังไงล่ะ ซึ่งเราจะเห็นว่า แค่เรื่องพื้นฐานแบบสุดๆ ก็ยังมีเรื่อง Data Structure & Algorithm รวมอยู่ด้วยเสมอ ดังนั้นไม่ว่าเราจะเขียนโค้ดด๋อยอะไรก็ตามของพวกนี้ก็จะตามเราไปทุกที่ ซึ่งถ้าเราไม่เข้าใจมัน การออกแบบ การทำ performance tuning หรืออะไรก็ตามก็จะมีปัญหาโดยที่เราไม่รู้ตัวนั่นเอง

    ข้อควรระวัง หลายคนพอเห็นว่ามันได้ตัวเลขเพี้ยนๆ ก็จะเบ้ปากแล้วบอกว่างั้นก็ใช้ decimal ไปเลยเด๊ปัญหาก็จบแล้ว ซึ่งแมวน้ำก็จะบอกว่า ใช่ครับมันแก้เรื่องตัวเลขเพี้ยนได้ แต่มันจำเป็นไหม? หรือมันเป็นแค่การซ่อนปัญหา?

    เกร็ดความรู้ (ทศนิยม) สาเหตุที่มันได้คำตอบเป็น 0.30000000000000004 ก็เพราะว่า Data Structure ของตัวเลขที่คอมมันเก็บนั้น มันอยู่ในรูปแบบของ binary (0/1) นั่นเอง และคอมมันทำงานกับตัวเลขที่เป็นทศนิยมไม่เก่ง เพราะมันจะเก็บค่าที่แท้จริงของทศนิยมไม่ได้ ดังนั้นมันจะเก็บเป็นค่าที่ใกล้เคียงที่สุด ดังนั้นเวลามันนำไปแสดงผลในบางทีมันจะมีปัญหาเรื่องการปัดเศษ ซึ่งเราเรียกปัญหาแบบนี้ว่า นั่นเองขอรับ

    เกร็ดความรู้ (Bitcoin) อย่างที่เกร็ดความรู้แรกบอกไปว่า คอมทำงานกับทศนิยมไม่เก่ง ยิ่งทำงานกับเลขทศนิยมมันจะยิ่งช้า ดังนั้นพวก Bitcoin เลยไม่ทำงานกับทศนิยมตรงๆ แต่จะเก็บเป็นตัวเลขจำนวนเต็มแทน (ตัวอย่างให้เห็นภาพสมมุตเขาจะเก็บ 1 บาท 25 สตางค์ ก็จะเก็บเป็น 12500) แล้วเมื่อไหร่ที่จะเอาไปแสดงผลก็ค่อยปัดตำแหน่งที่เป็นทศนิยมไปแสดงผลนั่นเอง (ตามตัวอย่างคือ 4 digits สุดท้าย 12500 ก็จะเป็น 1.25 บาท)

    จากที่เกริ่นมา Data Structure นั้นส่งผลกับการทำงานของซอฟต์แวร์เราโดยตรง ดังนั้นเราเลยจำเป็นต้องรู้ว่า Data Structure แต่ละรูปแบบมันมีจุดเด่นจุดด้อยยังไง เพื่อที่จะเลือกใช้มันได้ถูกตัว

    เช่น เราจะทำงานกับ collection แล้วเราจะเลือก Data Structure ตัวไหนล่ะ Array ดีไหม หรือ List ดีหว่า Stack ก็น่าสนใจนะ บลาๆ ซึ่งแน่นอนว่า Data Structure แต่ละตัวมีจุดเด่นที่ต่างกัน ซึ่งเราจะเลือกใช้ตัวไหนให้ดูง่ายๆจาก 2 อย่างคือ

    1.รูปแบบของปัญหา - เช่น ถ้าต้องประมวลผลข้อมูลแบบ FIFO (ทำตามลำดับ) หรือมาแบบ LIFO (มาหลังทำก่อน) การเลือก Data Type ผิดก็ทำให้เราเขียนโค้ดยากขึ้นเยอะ แถมยังช้าอีก

    2.รูปแบบของข้อมูล - เช่น ถ้าข้อมูลถูกเรียงลำดับมา หรือ ข้อมูลไม่มีลำดับเลย ก็จะมีผลเช่นกัน

    😒 แล้วเราจะรู้ได้ยังไงว่าเมื่อไหร่ควระเลือก Algorithm ตัวไหน? หรือใช้ Data Structure อันไหนดีกว่ากัน? . . . ไม่ต้องเสียเวลาคิดให้มากความ ไปรู้จักกับ Algorithm Complexity ในหัวข้อถัดไปกันเบย

    🤔 Algorithm ช้า/เร็ว ดูไง ?

    🔥 Algorithms Complexity

    สำหรับเพื่อนๆที่เข้าใจ Big-O อยู่แล้วก็ให้อ่านข้ามไปอ่านบทความถัดไปได้เลยที่บทความนี้ ส่วนใครที่อยากทบทวนก็อ่านต่อตรงนี้ละกัน โดยแมวน้ำขออธิบายโดยใช้คำถามง่ายๆด้านล่าง

    ถ้าเราจะคูณเลข 2 ตัว เรามีวิธีคิดแบบไหนได้บ้าง ? ตัวอย่าง 12 x 12 เราคิดยังไงให้ได้ผลลัพท์เป็น 144

    จากโจทย์ด้านบน เราก็อาจจะคิดง่ายๆได้ 2 วิธีตามด้านล่าง (จะหลายวิธีก็ได้แต่ขอตัดมาแค่นี้พอ)

    1.บวกซ้ำ - โดยเราก็จะนำ 12 มาบวกกัน 12 รอบ (12+12+12+....) ซึ่งก็จะได้ผลลัพท์เป็น 144 นั่นเอง 2.คูณทีละตำแหน่ง - โดยเราก็จะนำ 12 x 2 ก่อน แล้วค่อยเอาไปบวกกับ 12 x 10 ซึ่งก็ได้ผลลัพท์เป็น 144 เช่นกัน

    จากทั้ง 2 วิธี (Algorithm) ที่ว่ามา ถ้าเราลองทำตามดูเราจะพบว่า จำนวนครั้งที่ทำในแต่ละวิธีมันไม่เท่ากัน

    จากตารางด้านบนจะเห็นว่า วิธีบวกซ้ำ มันมีจำนวนครั้งที่ใช้เยอะกว่า วิธีคูณทีละตำแหน่ง ยิ่งตัวคูณเยอะเท่าไหร่ จำนวนครั้งที่ทำก็จะยิ่งเยอะตาม โดยในทางคอมพิวเตอร์ยิ่งจำนวณครั้งที่ทำสูงเท่าไหร่มันยิ่งช้า ดังนั้นเวลาที่เราจะเลือก Algorithm ให้เลือกจากจำนวนครั้งที่มันใช้ ซึ่งเราเรียกเจ้าตัวนี้ว่า Algorithms Complexity ซึ่งโดยปรกติเราจะเขียนมันด้วยสูตรที่เราเรียกมันว่า Big-O เพื่อเอาไว้บอกว่า Algorithm นั้นๆเร็วหรือช้ายังไง จากรูปด้านล่างจะเป็น Big-O ของแต่ละ Data Structure ของพวก Collection เพื่อช่วยในการตัดสินใจ เช่นจะใช้ Array หรือจะใช้ Stack ดี เราก็สามารถดูตารางด้านล่างเปรียบเทียบได้

    จากที่แมวน้ำมโนมาทั้งหมด สรุปได้ง่ายๆคือ Algorithm เร็วหรือช้า ไม่ได้ขึ้นอยู่กับ มันซับซ้อนหรือเปล่า เพราะต่อให้เราพยายามศึกษาแล้วก็ยังไม่เข้าใจมันเลย แต่ถ้าค่า Big-O ของมันน้อย มันจะทำงานได้เร็วกว่า Algorithm ประเภทเดียวกันที่มี Big-O เยอะๆนั่นเอง

    🍖 ตัวอย่างเพิ่มเติม

    ยังจำเรื่องการหา สมัยประถมกันได้ไหมเอ่ย? ซึ่งโดยปรกติที่เรียนมาเขาก็จะให้จับหารไปเรื่อยๆจนมันหารไม่ได้ แล้วเอาตัวที่หารไม่ได้ที่เหมือนกันไปคูณกันเพื่อได้ให้ผลลัพท์มาชิมิ ซึ่งนั่นก็เป็น 1 ใน Algorithm ที่สามารถทำได้ แต่ถ้าเราลองไปดู เราก็จะพบวิธีการหาเจ้า ห.ร.ม ในอีกรูปแบบที่ลดขั้นตอนลงไปได้เยอะเลยนั่นเอง ลองดูจากรูปด้านล่างก็ได้

    🎯 บทสรุป (part 1)

    ถึงตรงนี้ ดช.แมวน้ำ เชื่อว่าหลายคนน่าจะเข้าใจตรงกันละว่า Algorithm ช้าหรือเร็วขึ้นอยู่กับ Big-O หรือพูดอีกแบบคือ ยิ่ง execute statements เยอะยิ่งช้า และ แต่ละ Data Structure มันเก่งคนละด้าน ดังนั้นอย่าตะบี้ตะบันเอา Solution ที่เจอในเน็ทไปใช้กับงานที่ใช้จริง เพราะมันอาจจะไม่เข้ากับหน้างานเราก็ได้

    ข้อความระวัง แมวน้ำเห็นหลายครั้งที่บรรดาโปรแกรมเมอร์จะชอบเข้าใจผิดว่า ยิ่งโค้ดสั้นแสดงว่ามันยิ่งเร็ว ซึ่งถ้าได้อ่านบทความนี้แล้วก็ขอให้หยุดความคิดนั้นไปได้เลย เพราะ ความเร็วไม่ได้ขึ้นกับจำนวนบรรทัดของโค้ด มันขึ้นอยู่กับค่า Big-O เท่านั้น และต่อให้โค้ดคุณจะยาวแค่ 1 บรรทัด แต่ถ้า Big-O ของมันโคตรเยอะแบบ O(n!) มันก็จะช้ากว่าโค้ดที่เขียน 1,000 บรรทัดแต่มี Big-O น้อยๆเช่น O(n)

    แนะนำให้อ่าน สำหรับเพื่อนๆที่สนใจอยากรู้ว่าสิ่งที่โปรแกรมเมอร์มักเข้าใจผิดมีไรบ้าง ก็ไปตามอ่านได้จากบทความนี้เบย

    เดี๋ยวเราลองไปดูบทความถัดไปของ Algorithm ว่ามันไปเกี่ยวข้องกับพวกที่ทำ Security ยังไงกันบ้างดีกั่วกับบทความนี้

    Encapsulation

    🤔 มันคืออะไร ?

    คำว่า Encapsulation มีใช้อยู่ในหลายวงการเลย แต่ในวงการซอฟต์แวร์ใน Wikipedia ถูกเขียนไว้ว่า

    Encapsulation refers to the bundling of data with the methods that operate on that data, or the restricting of direct access to some of an object's components.[1] Encapsulation is used to hide the values or state of a structured data object inside a class, preventing unauthorized parties' direct access to them. Publicly accessible methods are generally provided in the class (so-called "getters" and "setters") to access the values, and other client classes call these methods to retrieve and modify the values within the object.

    ซึ่งถ้าถอดความหมาย เราก็จะได้หัวใจสำคัญของมันออกมาว่า

    Encapsulation เป็นการจัดการข้าวของภายในตัว Model ของเรา เพื่อให้คนอื่นเรียกใช้งานได้ง่ายๆ ในขณะที่เรายังป้องกันข้อมูลที่สำคัญจากภายนอกเอาไว้ได้

    🤨 ก็ยัง งง อยู่ดีขอตัวอย่างหน่อย

    สมมุติว่าเราต้องเขียน โปรแกรมบัญชีธนาคาร ซึ่งตัวบัญชีสามารถเก็บข้อมูล เจ้าของบัญชี และ เงินในบัญชี ได้ ดังนั้นเราก็น่าจะได้ Model จากหลักการของ Abstraction ออกมาประมาณนี้

    โค้ดตัวอย่างด้านบนไม่ได้มีอะไรผิดถูกต้องตาม Abstraction ทุกประการ แต่มันไม่ใช่ Model ที่ดีเพราะมันยังขาดการนำ Encapsulation มาใช้อยู่ ดังนั้นมาดูตัวอย่างของปัญหาเพื่อให้เห็นภาพกันว่ามันไม่ดีตรงไหน

    เพื่อนๆลองคิดถึงตอนที่เราจะใช้งานเจ้า BankAccount ดูนะ สมมุติว่าเราจะสร้างบัญชีให้ตัวผมเอง พร้อมฝากเงินเข้าไปในบัญชีนั้น 500 บาท ผมก็จะเขียนโค้ดง่ายๆออกมาได้ประมาณนี้

    Output Account: Saladpuk, has THB 500.

    ซึ่งดูเหมือนจะไม่มีปัญหาอะไรชิมิ งั้นลองเพิ่มเงินเข้าไป -1200 ดูดิ๊

    Output Account: Saladpuk, has THB -700.

    จะเห็นว่าเงินในบัญชีติดลบ -700 ไปเรียบร้อยแล้ว ซึ่งในบัญชีปรกติเราไม่ควรมีเงินติดลบอยู่ในนั้นใช่ป่ะ

    จากที่ว่ามาเดี๋ยวเราจะลองเอาหลักของ Encapsulation มาใช้ดูบ้างว่ามันจะมาช่วยเราได้ยังไง ซึ่งเราไปทำความเข้าใจแนวคิดของมันนิดหนึ่งก่อนว่า

    แนวคิด ในการทำ Model นั้นเราจะต้องแยกของที่เป็นข้อมูลที่ Sensitive data ออกมาดูแลเอง และให้เฉพาะคนที่สมควรเท่านั้น ถึงจะสามารถเข้าถึง sensitive data เหล่านั้นได้

    ซึ่งในโค้ดตัวอย่างสิ่งที่เป็น sensitive data ก็คือ เงินในบัญชี นั่นเอง ซึ่งเราไม่ควรให้คนอื่นเข้าไปแก้ไขมันได้มั่วๆ ดังนั้นเราต้องทำการซ่อนมันไว้ก่อน เลยทำให้ Model ของเราถูกแก้เป็นแบบนี้

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

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

    ดังนั้นจากที่ว่ามาเราก็ลองกลับไปลองแกล้งมันใหม่ดูว่าจะเกิดอะไรขึ้น

    Output Account: Saladpuk, has THB 500.

    จะเห็นว่าต่อให้เราฝากเงินติดลบลงไป บัญชีเราก็ไม่ทำงานผิดพลาดเหมือนเดิมแล้ว เพราะเรานำหลักของ Encapsulation มาใส่ในการออกแบบ Model แล้วนั่นเอง

    😵 Encapsulation คือไรกันแน่ ?

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

    ซึ่งคำว่า Encapsulation มันหมายถึง การเอาเข้าแคปซูล ตามรูปเลย

    โดยความหมายของมันจริงๆก็คือ

    • เราเปิดให้คนอื่นเข้าใช้งานได้เท่าที่เราอยากเปิด - ตามรูปคือโซนแคปซูลโปร่งใสที่มองเห็นตัวแปรและเมธอตที่เขาอยากให้เราเห็น

    • เราจะปิดข้อมูลในส่วนที่เราไม่อยากให้คนอื่นเข้ามายุ่ง - ตามรูปคือโซนแคปซูลสีแดงที่เรามองไม่เห็นเลยว่าข้างในมีอะไรอยู่

    ซึ่งหลักในการทำ Encapsulation เลยทำให้เราสรุปสั้นๆตามที่สรุปไว้ด้านบนสุดได้ว่า

    ✨Encapsulation เป็นการจัดการข้าวของภายในตัว Model ของเรา เพื่อให้คนอื่นเรียกใช้งานได้ง่ายๆ ในขณะที่เรายังป้องกันข้อมูลที่สำคัญจากภายนอกเอาไว้ได้

    ซึ่งหลักในการคิดเรื่อง Encapsulation ก็จะมีหลักในการทำ Information Hiding รวมอยู่ด้วย

    🤔 ทำ Encapsulation แล้วดีไง ?

    หนึ่งในข้อดีที่เราเห็นได้ชัดเจนคือ เราสามารถควบคุมสิ่งต่างๆจากภายนอกได้ แต่มันก็ยังมีอีกเรื่องนั่นคือ การทำ Encapsulation มันเกิดจากแนวคิดของ Component base ซึ่งคำว่า Component ในวงการนี้เราหมายถึง มันสมบูรณ์แบบด้วยตัวมันเอง นั่นหมายความว่า Model ที่เราสร้างไว้นั้น เราสามารถเอาไปใช้งานได้เลยไม่ต้องทำอะไรกับมันเพิ่มเติมอีกนั่นเอง ยกตัวอย่างง่ายๆคือ ถ้าเราอยากจะใช้ BankAccount แล้วต้องการฝากเงินเข้าไปก็แค่เรียกใช้แบบโค้ดด้านล่างได้เลย

    จากการใช้งานโค้ดด้านบนจะเห็นว่า เราไม่ต้องสนใจว่าโค้ดภายใน BankAccount มันทำงานจริงๆยังไง แต่เรารู้ว่าแค่เรียกใช้งานแบบนี้มันก็ทำการฝากเงินเข้าบัญชีให้เราได้ก็พอ เพราะว่า BankAccount มันเป็น Component ที่สมบูรณ์แบบด้วยตัวมันเองแล้ว

    ดังนั้นเวลาที่เราใช้งาน Component ต่างๆ เลยทำให้เราเขียนโปรแกรมได้ไวขึ้น เพราะแต่ละ Component มันก็สามารถทำงานได้โดยที่เราไม่ต้องไปยุ่งกับการทำงานภายในของมัน เรามีหน้าที่แค่เรียกใช้งานมันอย่างเดียวก็พอ สุดท้ายเราก็เอา Component ต่างๆมาใช้งานร่วมกันทำให้เกิดการทำงานแบบใหม่ได้นั่นเอง

    💡 หลักในการคิด

    เวลาที่เราจะทำ Encapsulation ให้ดูตัว Model ที่เรากำลังออกแบบก่อน ว่ามันมีข้อมูลที่เป็น sensitive data อะไรบ้าง? แล้วเราจะจัดการกับข้อมูลพวกนั้นยังไง? คนอื่นเข้าถึงได้ไหม? ถ้าเข้าถึงได้เราจะควบคุมคนที่เข้ามาเรียกใช้ยังไง? โดยทั้งหมดนี่มีวัตถุประสงค์เดียวคือ การควบคุม เพื่อให้เกิดเป็น Component ที่พร้อมนำไปใช้งานนั่นเอง

    ทองเก๊

    โจทย์สอบสัมภาษณ์เข้า Saladpuk

    🥳 โจทย์

    ตรงหน้ามีทองอยู่ทั้งหมด 12 แท่งที่มีหน้าตาเหมือนกันเด๊ะๆ แต่ว่าหนึ่งในนั้นเป็นของปลอม เรารู้แค่ว่าทองของแท้มีน้ำหนักเท่ากันเสมอ ส่วนทองของปลอมอาจจะหนักกว่าหรือเบากว่าทองของแท้ แต่ไม่มีทางที่จะหนักเท่ากันแน่ๆ ใกล้ๆตัวเรามีตราชั่งสมมาตรอยู่ 1 อันที่จะเอามาชั่งกี่ครั้งก็ได้ เราจะชั่งอย่างน้อยที่สุดกี่ครั้งเพื่อหาทองปลอมชิ้นนั้นออกมา?

    โจทย์ทุกข้อ ดช.แมวน้ำ ไม่ได้อยากได้ตัวเลขที่เป็นคำตอบ แต่อยากได้วิธีทำ เพราะมันคือ Logic ขั้นพื้นฐานของการแก้ไขปัญหา และ การอธิบายนี่แหละคือจุดอ่อนของเหล่าโปรแกรมเมอร์ เพราะสปีชีส์โปรแกรมเมอร์ส่วนใหญ่จะสื่อสารภาษามนุษย์กันไม่ค่อยเป็นเท่าไหร่นั่นเอง 😂

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

    🤠 วิธีคิด

    โจทย์ข้อนี้จริงๆมันซ่อนความยากไว้ 2 อย่าง อันแรกคือ ทองแท่งไหนคือของปลอม ซึ่งการที่จะหามันได้เราจะ ต้องรู้ว่าทองปลอมมันหนักกว่าหรือเบากว่า ด้วยนั่นเอง

    🚀 เฉลยไว้ก่อนเลยว่าเราสามารถหาคำตอบได้ 100% สำหรับทุกกรณีคือการชั่ง 3 ครั้ง

    • 💡 การชั่งแต่ละครั้งโคตรสำคัญเพราะมันเป็นจุดใช้ติดสินใจการชั่งครั้งถัดไป ดังนั้นทุกการชั่งควรต้องได้ infomation ที่เอาไปคิดต่อได้เสมอ ซึ่งการที่จะทำแบบนั้นได้จุดเริ่มต้นของมันคือ การแบ่งกลุ่ม

    • 💡 (ข้อนี้สำคัญที่สุด) ทองแท้เท่านั้นที่มีคุณสมบัติในการเปลี่ยนกลุ่มแล้วได้ผลลัพท์เหมือนเดิม ทองปลอมไม่มีคุณสมบัตินี้ เพราะทองแท้ไม่ว่าจะเปลี่ยนไปวางไว้ในกลุ่มไหน ผลลัพท์ในการชั่งต้องได้เหมือนเดิมเสมอ

    • 💡 ถ้าเราชั่งครั้งที่ 2 เสร็จ แต่ยังหาความสัมพันธ์เรื่องน้ำหนักไม่ได้ เราก็จะไม่มีทางตอบคำถามข้อนี้ด้วยการชั่ง 3 ครั้งได้เลย

    🔥 แบ่งกลุ่ม

    ทอง 12 แท่งสามารถจัดกลุ่มได้หลายแบบ ซึ่งแมวน้ำขอยกตัวอย่างแค่ 4 แบบให้ดูละกัน ตามรูปด้านล่าง

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

    🤠 ตัวเลือกถัดมาที่ควรตัดออกคือแบบ 6 กลุ่ม เพราะจำนวนกลุ่มมันเยอะเกินไป สมมุติง่ายๆว่าเราโชคร้ายไปหยิบเจอกลุ่มที่เป็นของปลอมได้ตอนท้ายๆ เราก็ต้องชั่งไปอย่างน้อย 3 ครั้งละ ดังนั้นไม่มีทางที่เราจะหาคำตอบได้ภายในการชั่ง 3 ครั้งแน่นอน

    🤠 ถัดมาถ้าเราคิดจากกรณีโชคร้ายก็จะทำให้เรารู้ว่า ควรตัดแบบ 4 กลุ่มทิ้ง เพราะถ้าชั่งครั้งแรกแล้วมันหนักเท่ากัน เราจะได้ข้อมูลกลับมาน้อยกว่าแบบ 3 กลุ่ม ในกรณีที่โชคร้ายเหมือนกัน ตามรูปด้านล่าง (ถ้าการชั่งไหนได้ information กลับไปน้อยถือว่าเสียประโยชน์ เพราะจำนวนในการชั่งมีค่าเยอะกว่าม๊ากกก)

    บางคนอาจจะเถียงข้อนี้ก็ไม่เป็นไร เพราะกรณีแย่สุดๆของการแบ่ง 4 กลุ่มคือ ชั่งครั้งที่ 1 เท่ากัน แล้วชั่งครั้งที่ 2 ก็เท่ากันอีก เราจะหาทองปลอมไม่ได้ครับทุกกรณีนั่นเอง (เฉลยขู่ไว้ก่อนไม่เชื่อลองทำดู 🤣)

    🤠 ดังนั้นจากที่ว่ามาเลยทำให้ตัวเลือกเราเหลือแค่การแบ่งเป็น 3 กลุ่มนั่นเองกั๊ฟ

    🔥 ตราชั่ง

    ก่อนจะเอามันไปขึ้นเขียง ดช.แมวน้ำ ขอตั้งชื่อมันให้ง่ายต่อความเข้าใจหน่อยนุง ตามภาพด้านล่าง

    คราวนี้เราก็หยิบมันมาขึ้นเขียงซะ!! โดยแมวน้ำขอหยิบ A กับ B ละกัน ซึ่งผลลัพท์เป็นไปได้แค่ 3 แบบคือ หนักกว่า เบากว่า และ เท่ากัน ดังนั้นก็จะเป็นตามรูปด้านล่าง

    จากรูปด้านบนก็จะทำให้เรารู้ว่า ในกรณีที่ 1 กับ 2 นั้นกลุ่ม C เป็นทองแท้ทั้งหมด ส่วนกรณีที่ 3 กลุ่ม C มีทองปลอมอยู่แน่ๆ ตามรูปด้านล่าง

    (กรณีที่ 3 แก้ง่ายแม้วน้ำเลยขอไปแก้ของยากๆก่อนนะ 🤣) จากตรงนี้ขอเอา กรณี 1 มาทำต่อ โดยขอซูมเข้าไปในรูปอีกนิสก็จะเห็นเลขที่ติดไว้บนทองแต่ละแท่งตามนี้

    🤠 ในการชั่งครั้งที่ 2 นี้สำคัญที่สุด ซึ่งเราต้องบีบตัวเลือกให้เหลือทองน้อยกว่า 4 อัน เพราะไม่งั้นการชั่งครั้งที่ 3 จะหาคำตอบไม่ได้

    ไม่เชื่อลองจินตนาการดูก็ได้ว่า ถ้าเหลือทอง 4 อัน (ต่อให้รู้ว่าทองปลอมหนักหรือเบากว่าก็ได้) แต่ให้ชั่งได้ครั้งเดียวเราจะหาคำตอบแบบ 100% ได้ยังไง?

    จากที่ว่ามาเราเลยต้องทำการเลือกบางส่วนที่จะไม่นำมาชั่ง และอันที่จะชั่งจะต้องสลับระหว่างกลุ่ม A กับ B (เดี๋ยวอธิบายตอนเห็นตัวอย่างจะเข้าใจง่ายกว่า) ดังนั้นเราเลยเลือกทองที่จะชั่งในครั้งที่ 2 เป็นตามรูปด้านล่าง

    จากแนวคิดที่ว่า 💡 ทองแท้เท่านั้นที่มีคุณสมบัติในการเปลี่ยนกลุ่มแล้วได้ผลลัพท์เหมือนเดิม ทองปลอมไม่มีคุณสมบัตินี้ หมายถึงเอาทองแท้กับทองแท้สลับกันผลลัพท์ต้องเหมือนเดิม แต่ถ้าเอาของแท้กับของปลอมสลับกันผลลัพท์จะไม่เหมือนเดิม (เพราะเรื่องนี้แหละเราเลยต้องชั่งน้ำหนักแบบสลับด้านกัน) ดังนั้นจะทำให้เรารู้ว่า

    • (กรณีที่ 4) ตาชั่งเอียงทิศทางเดิม แสดงว่า B1, B2, A2 เป็นทองจริง เพราะพวกมันถูกสลับตำแหน่งมาแล้วยังคงให้ผลลัพท์เหมือนเดิม เลยทำให้เรารู้ว่า ทองปลอมต้องอยู่ใน A1, B3, B4

    • (กรณีที่ 5) ตาชั่งเอียงเปลี่ยนทิศ เลยทำให้เรารู้ว่า ทองปลอมต้องอยู่ใน B1, B2, A2 เพราะมันสลับตำแหน่งกันแล้วผลลัพท์เปลี่ยนไปจากเดิม

    • (กรณีที่ 6) หนักเท่ากัน แสดงว่า ทองปลอมต้องอยู่ในกลุ่มที่ไม่ได้เอามาชั่งนั่นคือพวก A3, A4

    จากที่ว่ามาก็จะได้ออกมาตามรูปด้านล่าง + เติมสีแดงช่วยให้จำง่ายขึ้นว่าในการชั่งครั้งที่ 1 ฝั่งไหนหนักฝั่งไหนเบาลงไปด้วย

    (กรณีที่ 3 แก้ง่ายแม้วน้ำเลยขอไปแก้ของยากๆก่อนนะ 🤣) จากตรงนี้ขอเอา กรณี 4 มาทำต่อ โดยการชั่งครั้งแรกกลุ่ม B บอกว่ามันเบา ดังนั้นเพื่อเป็นการพิสูจน์ว่าไม่มีทองปลอมปนอยู่ในกลุ่มนี้นี้คือทุกตัวในกลุ่มนี้ต้องหนักเท่ากัน เลยเอา B3 ไปชั่งกับ B4 ซึ่งผลที่ได้ก็จะมี 3 กรณีคือ

    • หนักเท่ากันแสดงว่ากลุ่ม B ทั้งหมดเป็นทองแท้ ดังนั้นทองปลอมคือ A1 และมีน้ำหนักมากกว่าปรกติ

    • B3 หนักกว่า B4 แสดงว่า ทองปลอมมีน้ำหนักเบากว่าปรกติ และมันคือ B4 เพราะจากด้านบนพิสูจน์แล้วว่ากลุ่มนี้โกหก

    • B3 เบากว่า B4 แสดงว่า ทองปลอมมีน้ำหนักเบากว่าปรกติ และมันคือ B3 เพราะจากด้านบนพิสูจน์แล้วว่ากลุ่มนี้โกหก

    ซึ่งก็จะได้ผลลัพท์ตามนรูปด้านล่าง

    ซึ่งพอได้คำตอบตรงนี้เราก็จะรู้ว่า กรณีที่ 5 และ 6 ก็สามารถหาคำตอบได้โดยใช้ทำตามแบบกรณีที่ 4 ได้เลย ส่วนกรณี 2 ก็จะเป็นด้านตรงข้ามกับกรณี 1 ดังนั้นลอกลงมาแล้วกลับด้านก็จะได้คำตอบเหมือนกัน ซึ่งก็จะเหลือแค่กรณี 3 อันสุดท้าย แต่แมวน้ำเชื่อว่าเพื่อนๆก็ทำได้อยู่แล้วแน่ๆเลยขอไม่อธิบายต่อนะ ดูในรูปสรุปด้านล่างเอาละกันเพราะเรื่องที่ยากที่สุดแก้ไปเรียบร้อยแล้ว

    ดังนั้นเราก็จะสามารถหาทองปลอมได้ไม่ว่าจะเป็นกรณีไหนก็ตาม แถมบอกได้เสมอว่ามันน้ำหนักเบากว่าหรือมากกว่าปรกตินั่นเองขอรับ

    🎯 ข้อคิดที่ได้

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

    Algorithm P & NP

    🤔 คอมมันหารเลขยังไงหว่า ?

    จากบทความ 👾 Algorithm Big-O น่าจะทำให้เหล่าแมวน้ำหลายคนตาสว่างขึ้นว่า การเขียนโค้ดสั้นๆไม่ได้หมายความว่ามันจะเร็วกว่าโค้ดยาวๆเสมอไป เพราะมันวัดกันที่จำนวน Execution ต่างหาก ดังนั้นในรอบนี้เราจะมาต่อกันให้จบพื้นฐานว่า Algorithm มันลากยาวไปจนถึงเรื่องการทำ Security ได้ยังไง และ หัวใจของ Algorithm คืออะไร ?

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของบทความ 👶 Data Structure & Algorithm หากเพื่อนๆสนใจอยากรู้ว่าเจ้า Algorithm มันคืออะไร คุ้มค่าเวลาชีวิตที่เราจะอ่านไหม ก็สามารถศึกษาได้จากบทความหลักได้เลยโดยการคลิกที่ชื่อบทความสีฟ้าๆนั่นแหละ

    🥱 หารเลข ป.2

    รอบนี้เรามาลองหารเลขดูบ้าง โดยแมวน้ำมีคำถามว่า

    ถ้าเราจะหารเลข 2 ตัว เรามีวิธีคิดแบบไหนได้บ้าง ? ตัวอย่าง 12 / 2 เราคิดยังไงให้ได้ผลลัพท์เป็น 6

    จากโจทย์ด้านบน เราก็อาจจะคิดง่ายๆโดยการ ลบซ้ำไปเรื่อยๆ เช่น เราก็จะเอา 12 มาลบกัน 2 ไปเรื่อยๆจนกว่าจะลบไม่ได้ แล้วนับเอาว่าลบได้กี่ครั้ง ซึ่งก็จะได้ผลลัพท์เป็น 6 ตามด้านล่างนั่นเอง

    (ครั้งที่ 1) 12 - 2 = 10 (ครั้งที่ 2) 10 - 2 = 8 (ครั้งที่ 3) 8 - 2 = 6 (ครั้งที่ 4) 6 - 2 = 4 (ครั้งที่ 5) 4 - 2 = 2 (ครั้งที่ 6) 2 - 2 = 0 ทำต่อไม่ได้ละ ดังนั้นตอบ 6 ยังไงล่ะจ๊ะ

    เหมือนจะไม่มีไรเรยชิมิ? แต่เคยสงสัยไหมว่า คอมมันหารเลขยังไงหว่า 🤔 ??

    ต่อให้เราเขียนโค้ดในระดับ Machine Language อย่าง Assembly สุดท้ายเรารู้อ่ะป่าวว่ามันไปทำงานยังไง?

    🤨 คอมคิดเลขยังไง ?

    หัวข้อนี้กึ่งๆเป็นเกร็ดความรู้ เบื่อก็อ่านข้ามได้ แต่มันคือพื้นฐานการคำนวณของคอมพิวเตอร์เบย

    จากเรื่องง่ายๆที่คนทำได้สบายๆ เชื่อไหมว่าคอมมันทำงานวุ่นวายกว่านั้นเยอะ เพราะ คอมมันทำงานกับตัวเลขฐานสอง หรือ binary (0/1) นั่นเอง ดังนั้นการคำนวณที่แท้จริงมันจะใช้พวก Logic gate ต่างๆ ( AND, OR, NOT บลาๆ) เช่นเราจะสั่งให้ A + B มันก็จะต้องเอา 2 ค่านี้ไปเข้าวงจร เพื่อให้มันได้ผลลัพท์ออกมาตามรูปด้านล่าง

    อย่าพึ่งปิดหนีนะ 😭 แมวน้ำไม่ได้จะลงลึกของพวกนี้หรอก แต่อยากให้เข้าใจแนวคิดพื้นฐานก่อน แล้วจะถึงบางอ้อว่า หัวใจ Algorithm คืออะไรโดยไม่ต้องมานั่งจำอะไรเลย

    ทีนี้เหล่าแมวน้ำก็รู้แล้วนะว่า คอมมันใช้วงจรไฟฟ้าคิดเลข ดังนั้นเครื่องคอมเลยมีแต่วงจรยั้วเยี๊ยไปหมดงุยล่ะ และถ้าเราอยากให้มันเก่งขึ้น เราก็จะเพิ่มวงจรให้เยอะขึ้น แต่สิ่งที่แลกมาก็คือ มันจะร้อนขึ้น + กินไฟมากขึ้น + กินพื้นที่ใส่วงจรมากขึ้น นั่นเอง

    เกร็ดความรู้ จากปัญหาความร้อน กินไฟบลาๆ ซึ่งแน่นอนว่าพวกวิศวะก็รู้เรื่องเหล่านี้ดี เขาเลยแบ่งตัวประมวลผล หรือ ออกเป็น 2 ตระกูล

    1. (CISC) Complex Instruction Set Computer - มีวงจรค่อนข้างเยอะ

    2. (RISC) Reduced Instruction Set Computer - มีวงจรไม่เยอะ

    👾 P & NP

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

    จงหาคำตอบว่า 390 / 13 ได้เท่าไหร่เอ่ย ?

    จากโจทย์ด้านบนถ้าให้คอมพิวเตอร์ไปหาคำตอบ มันจะใช้เวลานาน (ในเชิงคอมพิวเตอร์) แต่ถ้าเราบอกมันว่า ผลลัพท์คือ 30 เข้าไปปุ๊ป คอมมันจะตอบได้ทันทีเลยว่าคำตอบนั้นถูกหรือผิดภายในเวลาไม่นานนั่นเอง

    เพราะมันแค่นำ 13 x 30 ก็จะได้คำตอบเป็น 390 ดังนั้นคำตอบถูกต้องแน่นวล

    แต่ในทางตรงกันข้ามถ้าจะให้คอมไปหาคำตอบว่า 390 / 13 ได้เท่าไหร่ล่ะก็ (คิดแบบคนเข้าใจง่ายๆนะ) ถ้าเอาแบบเด็กสุดก็นั่งท่องสูตรคูณแม่ 13 ตามด้านล่างเลยครัช

    13 x 1 = 13 13 x 2 = 26 13 x 3 = 39 ... 13 x 30 = 390 ! ได้คำตอบแล้วเฟร้ยยยย น้ำตาจิไหล

    เกร็ดความรู้ คอมเวลาที่มันต้องหารเลขจริงๆ มันมีสิ่งที่เรียกว่า Lookup table หรือพูดแบบบ้านๆคือมันมี ตารางแม่สูตรคูณ นั่นแหละ เวลามีคนถามอะไรไป มันก็ไปหาจากตารางแม่สูตรคูณแล้วก็เอามาตอบ ก็จะไม่เสียเวลามากงุยล่ะ (มันไม่ได้ทำแบบนี้เสมอไปนะ แมวน้ำจำไม่ได้ส่งคืนอาจารย์ไปหมดแล้ว ใครรู้เมนต์บอกด้วยกะดี)

    ก็น่าจะพอเห็นภาพแล้วนะว่า ถ้าต้องการหาคำตอบซักอย่างมันจะมีแบบ ใช้เวลาสั้นๆก็ได้คำตอบ และ ใช้เวลาเยอะๆถึงได้คำตอบ เราก็จะแบ่งระดับความซับซ้อนได้ง่ายๆ เป็น 2 แบบคือ

    1. Polynomial - ไม่ซับซ้อนมาก ใช้เวลาสั้นๆ

    2. Nondeterministic Polynomial - ซับซ้อนม๊วก ใช้เวลานานม๊วก

    เกร็ดความรู้ ถ้าในใจอยากรู้ลึกๆในเรื่องการคำนวณของพวกนี้ สามารถเข้าไปอ่านต่อได้ในเรื่องของ ซึ่งระเอียดยิบเลย เช่น Zero-error Probabilistic Polynomial , Randomized Polynomial , Bounded-error Probabilistic Polynomial , Bounded-error Quantum Polynomial เผื่อใครอยากไปดูปัญหาของควอนตัมไรงี้

    จากที่เกริ่นมายาวเหยียดก็ได้เวลาเข้าสู่ประเด็นที่แมวน้ำจะพูดละ นั่นคือ . . .

    🔥 Algorithm บางอย่างมันมีทั้ง P และ NP

    นั่นหมายความว่าการคำนวณบางอย่างถ้า ทำแบบโง่ๆมันก็จะช้า แต่ถ้าเราคิดวิธี ทำแบบฉลาดๆมันก็จะเร็ว เช่นโจทย์ ป.6 ด้านล่าง

    เลข 100 เป็นจำนวนเฉพาะหรือเปล่า? (Prim number) (จำนวนเฉพาะคือ เลขที่ไม่มีใครสามารถหารมันได้ลงตัว ยกเว้น 1 กับตัวมันเอง เช่น 7 เป็นจำนวนเฉพาะ เพราะมีแค่เลข 1 กับ 7 เท่านั้นที่หารมันลงตัว)

    Algorithm 1.ทำแบบไม่ได้คิดอะไรมาก เราก็จะเอาเลขทั้งหมด 2, 3, 4, ... 99 ไปหารมันแล้วดูว่ามีตัวไหนหารมันได้หรือเปล่า ดังนั้นวิธีนี้แบบเลวร้ายสุดเราก็จะต้องจับหารไปเรื่อยๆ 98 ครั้ง (N-2) ดังนั้นก็ค่อนข้างช้าอยู่

    Algorithm 2.ตัดเลขที่ไม่จำเป็นออก ถ้าเราคิดต่อจากวิธีแรกดีๆเราก็จะรู้ว่า เลขคู่ ไม่จำเป็นต้องนำไปหารก็ได้ เพราะเลข 2 สามารถหารเลขคู่ได้ทุกตัว ดังนั้นเราก็เอา 2, 3, 5, 7, 9, ... 99 ไปหารก็จะได้คำตอบเหมือนกัน ดังนั้นวิธีนี้จะเร็วกว่าวิธีแรกเท่าตัว เพราะมันทำแค่ 50 ครั้ง (N/2)

    Algorithm 3.ตัดเลขที่ไม่มีทางหารมันได้ลงตัวออก สุดท้ายถ้าเราเพ่งกสิณก็จะค้นพบว่า ถ้าเลขเกินช่วงนึงไปแล้วมันจะไม่มีทางเอาไปหารลงตัวได้เลย เช่นถ้าเกิน 50 ไปแล้วก็ไม่มีทางหาร 100 ลงตัวแน่ๆ ซึ่งจุดที่เอาไว้บอกว่าควระหยุดเมื่อไหร่นั่นก็คือ √N (รูท 2 ของ N) นั่นเอง ดังนั้นเราก็จะเอา 2, 3, 5, 7, 9 ไปหารก็พอ ซึ่งมันก็จะทำแค่ 5 ครั้งเท่านั้น

    จากตัวอย่าง Algorithm ทั้ง 3 แบบเราก็จะพบว่า เรื่องการหาจำนวนเฉพาะ เราจะทำแบบ P หรือแบบ NP ก็ได้ ขึ้นอยู่กับเราคิดวิธีการคำนวณออกหรือเปล่าเท่านั้นเอง

    🔥 Algorithm บางอย่างมีแต่แบบ NP เท่านั้น

    หมายความว่าโจทย์บางอย่าง เราไม่มีวิธีลัด ต้องค่อยๆทำไปเรื่อยๆเท่านั้นถึงจะได้คำตอบ แต่ในบางทีเราก็สามารถพิสูจน์ได้ว่าคำตอบนั้นถูกหรือไม่ด้วยการใช้ P ได้ เช่น ตัวอย่างการหารเลขด้านบน หรือ การทำ Digital Signature เราต้องใช้ขั้นตอนในการสร้างนาน แต่เราสามารถพิสูจน์ได้ง่ายๆว่าคนที่ sign key นั้นมี key จริงๆหรือเปล่านั่นเอง

    แนะนำให้อ่าน สำหรับเพื่อนๆที่สนใจอยากเข้าใจเรื่อง Digital Signature สามารถไปอ่านต่อกันได้ที่บทความในลิงค์นี้เลยฮั๊ฟ

    👻 NP-completeness

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

    เกร็ดความรู้ อย่างที่บอกไปว่า NP-completeness เป็นแค่สิ่งที่อยู่ในทฤษฎีเท่านั้น ยังไม่มีคนสามารถพิสูจน์ได้ว่ามันมีของแบบนี้อยู่จริงหรือไม่ แต่ว่าหัวใจหลักของการทำ Security คือการทำสมการให้เป็น NP-completeness ตัวนี้แหละ เพราะเหล่าแฮกเกอร์จะต้องใช้เวลาในการถอดรหัสนานมากนั่นเอง ดังนั้นถ้ามีใครสักคนไปพิสูจน์ได้ว่า NP-completeness ไม่มีอยู่จริง มันจะทำให้ Security ทั่วโลกพัง เพราะมันหมายความว่า Algorithm ที่เราเชื่อว่ามันถอดรหัสยาก จริงๆแล้วไม่ได้ยากแบบนั้น แค่ยังไม่มีคนหารูปแบบการถอดรหัสที่อยู่ในรูปของ P เจอเท่านั้นเอง

    🎯 บทสรุป

    Algorithm คือบนโลกนี้แบ่งง่ายๆเป็น 2 อย่าง (P) แก้ได้เร็ว กับ (NP) แก้ได้ช้า ดังนั้นเวลาที่เราจะเลือกใช้ Algorithm อะไรก็ตาม เราอาจจะต้องดูความยากง่ายในการแก้ไขปัญหาของมันด้วย

    และในทางตรงข้ามของที่แก้ได้ช้า NP ในบางทีก็สามารถพิสูจน์คำตอบได้แบบ P เร็วเช่นกัน ดังนั้นเราไม่จำเป็นต้องนั่งถึกทำกระบวนการเดิมซ้ำเพื่อตรวจคำตอบแบบ NP ก็ได้

    เทคนิคในการออกแบบ

    Image by Alexas_Fotos on Pixabay

    หลังจากที่ได้เห็นตัวอย่างในการนำหลัก (OOP) Object-Oriented Programming ไปใช้ในการเขียนโค้ดของเราแล้ว ซึ่งจะเห็นว่าการออกแบบที่ดีนั้นทรงพลังอย่างมาก เพราะมันจะช่วยให้เวลามีความต้องการใหม่ๆเข้ามาปุ๊ป เราอาจจะแก้ไขโค้ดนิดหน่อยก็ปิดงานได้แล้ว หรือถ้าโชคดีมากก็อาจจะไม่ต้องทำอะไรเลยงานก็ปิดทันทีนั่นเอง ดังนั้นในรอบนี้เราจะมีปิดท้ายกันในเรื่องเทคนิคในการนำ OOP ไปใช้กันบ้างนะ

    ❤️ หัวใจหลักของ OOP

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

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

    ✔️ อย่าใช้ Inheritance มั่วซั่ว

    หลายคนอ่านมาถึงจุดนี้ก็อาจจะ งงๆ เพราะ Inheritance เป็นหนึ่งในหัวใจหลักของ OOP เลยนิ แต่ทำไมไม่ให้เราใช้มันล่ะ?? ซึ่งถ้าเราจะเข้าใจถึงแก่นแท้ของ Tip เรื่องนี้ได้ เราต้องเข้าใจ ข้อดี และ ข้อเสีย ของการทำ Inheritance เสียก่อนนั่นเอง

    👍 ข้อดีของ Inheritance

    • เราสามารถลดโค้ดที่มันทำงานเหมือนๆกันได้ โดยใช้ Base class เป็นตัวจัดการนั่นเอง

    • โค้ดถูกเพิ่มเติมความสามารถใหม่ๆเข้าไปได้ โดยที่ไม่ต้องไปแก้โค้ดเดิมเลย

    👎 ข้อเสียของ Inheritance

    • การทำ Inheritance ที่ถูกหลัก มันทำยาก เพราะต้องเข้าใจและแยกความสัมพันธ์แบบ IS A กับ HAS A ได้อย่างถูกต้อง ถึงจะ มีโอกาสออกแบบได้ถูก นั่นเอง ซึ่งในโจทย์จริงบางทีมันไม่ได้แยกกันได้ง่ายเหมือนในตัวอย่าง

    • เปลี่ยนแปลงแก้ไขยาก ลองคิดดูว่าถ้าโค้ดเราใช้ Inheritance กับคลาสหลายๆตัว แต่อยู่มาวันหนึ่งเราพบว่า เราไม่ควรทำโครงสร้างแบบนั้น หรือ อยากแก้โครงสร้างพวกนั้นล่ะ มันอาจจะต้องไปไล่แก้เป็นร้อยๆคลาสเลยก็เป็นได้ แล้วถ้าเกิดเราทำเป็น Library ไว้ แล้วคนที่เอาไปใช้ต่อนี่ต้องมาคอยไล่แก้ตามโครงสร้างใหม่เราด้วยนะอย่าลืม

    • คนเอาไปใช้ต่อเอาไปใช้ยาก ลองคิดดูว่าถ้าเราจะต้องไปใช้คลาสที่คนอื่นเขียนไว้เราใช้ได้เลยไหม? ซึ่งคำตอบส่วนใหญ่ก็คือต้องไปทำความเข้าใจมันก่อน แต่มันจะยิ่งยากขึ้นหลายเท่าเมื่อมันเป็นโครงสร้างแบบ Inheritance นั่นเอง

    จากตรงนี้เราก็น่าจะเห็นภาพคร่าวๆกันละว่า ถ้าเราใช้ Inheritance ไม่ถูกหลักแล้วล่ะก็ มันจะทำให้โครงสร้างของโค้ดเราซับซ้อนโดยไม่จำเป็น แถมยังกระทบถึงคนอื่นๆที่เอาของพวกนั้นไปใช้ด้วยนั่นเอง ดังนั้นก่อนทำ Inheritance เราจะต้องเคลียความเข้าใจเรื่อง IS A และ HAS A ให้เรียบร้อย พร้อมกับ มีเหตุผลที่ชัดเจนพอ ที่จะนำมันมาใช้นั่นเอง

    ✔️ Composition over Inheritance

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

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

    😈 ความสัมพันธ์แห่งนรก

    (รอบนี้ขี้เกียจทำรูปเองขอใช้จาก เลยละกัน) อย่างที่บอกไปว่าถ้าเราแบ่งของออกเป็นกลุ่มๆมันจะทำให้โครงสร้างมันซับซ้อนไปเรื่อยๆ เช่น กลุ่มของเป็ด (Duck) ซึ่งเป็ดแต่ละสายพันธ์ก็มีรูปร่างที่ไม่เหมือนกัน เช่น เป็ดน้ำ (Mallard), เป็ดหัวแดง (Readhead), เป็ดยาง (Rubber), เป็ดล่อเป้า (Decoy) ซึ่งเจ้าของพวกนี้เรามองว่ามันเป็นเป็ดทั้งหมดเลยทำ inheritance มันมาซะเลย

    อ่อ อย่าลืมนะว่าเป็ดแต่ละแบบมันร้องไม่เหมือนกันถ้าจะทำหลัก Inheritance จริงๆแล้วล่ะก็มันก็ต้องเป็นกลุ่มของมันเองนิน่า ดังนั้นก็จัดกลุ่มเรื่องของการ ส่งเสียงร้อง (Ducklike) ซึ่งเสียงร้องมีหลายแบบ (Quackable) ดังนั้นก็จัดเลย

    อ่อแล้วก็อย่าลืมนะว่าเป็ดมันมีปีกดังนั้นก็จะกลุ่มความสามารถของ การบิน (Flyable) ด้วยนะ ตามรูปด้านล่างเบย

    สุดท้ายก็เอาของต่างๆที่จัดกลุ่มมายำรวมกันก็จะได้โครงสร้างเป็ดรวมมิตรตามรูปด้านล่าง (ของอื่นๆที่ไม่ได้อธิบายก็ปล่อยมันไปนะเดี๋ยวมันจะยาวกว่านี้)

    แค่เห็นรูปด้านบนก็สยองแล้ว ความสัมพันธ์ต่างๆเต็มไปหมดเบย แล้วถ้าเราจะเพิ่มความสามารถใหม่ๆ หรือ กลุ่มใหม่ให้กับเจ้าเป็ดพวกนี้เราจะเริ่มที่ไหนต่อดีเอ่ย ?? นี่แหละคือสิ่งที่เรียกว่า ความสัมพันธ์แห่งนรก

    🎎 Composition

    แทนที่เราจะใช้ Inheritance รัวๆ เราก็หันมาใช้ Composition หรือที่เรียกว่าความสัมพันธ์แบบ HAS A จะทำให้โครงสร้างเราง่ายขึ้นเยอะเลย เช่น กลุ่มของการบิน และ กลุ่มของการร้อง เราก็ยังคงมีไว้เช่นเดิม ตามรูปด้านล่าง (นึกว่าจะไม่ได้ทำรูปซะละสุดท้ายก็ได้ทำ -_-'')

    แต่เราจะมองเจ้า 2 กลุ่มนั้นเป็นแค่เพียง ความสามารถ ที่เป็น คนละเรื่องกับเป็ด นั่นเอง ดังนั้นมุมมองจะถูกเปลี่ยนไปเป็นตามรูปนี้ (นึกว่าจะไม่ได้ทำรูปซะละสุดท้ายก็ได้ทำ -_-'')

    จากการเปลี่ยนความสัมพันธ์ในเชิง Inheritance ให้กลายมาเป็น Composition ตามรูปด้านบน มันจะทำให้การเกิด Combinations ได้มากมาย และที่สำคัญคือ เราสามารถเปลี่ยนพฤติกรรมการทำงานของ object ได้ระหว่าง Runtime เลยนั่นเอง

    ใน UML ด้านบนจะเปลี่ยนไม่ได้นะขี้เกียจแก้ละ ลองทำเป็น setter method ไว้ดูเอาละกัน

    ✔️ Guideline ในการทำ Inheritance

    • อย่าใช้มันเลยถ้าไม่จำเป็นจริงๆ

    • ถ้าจำเป็นต้องใช้จริงๆ ให้ลองคิดในมุมของ Composition เสียก่อน ซึ่งถ้าใช้แทนกันได้ ขอแนะนำว่าให้ทำเป็น Composition จะดีกว่า

    • ดูความถูกต้องก่อนทำ Inheritance เช่น รถตักดิน สามารถ inherit มาจาก กลุ่มของรถยนต์ ได้ เพราะมันเป็นรถยนต์ แต่ รถไฟเหาะตีลังกา มันไม่ใช่รถยนต์ ดังนั้นอย่าเอามันมา inherit เชียว เพราะมันอยู่กันคนละกลุ่มกัน

    🎯 บทสรุป

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

    เก็บรูปในฐานข้อมูล

    🤔 ไฟล์รูปปรกติเขาเก็บกันยังไงนะ ?

    😥 ปัญหา

    เวลาที่ทำแอพอะไรก็ตาม แล้วมันต้องมีการเก็บรูปที่ผู้ใช้อัพโหลดเข้ามา ดช.แมวน้ำ แมวน้ำเจอบ่อยมากที่ developer จะชอบเอาไฟล์รูปไปเก็บไว้ใน database ! 😱 ซึ่งถามว่าทำได้ไหม? คำตอบคือทำได้ครับ แต่มันจะเหมาะสมหรือเปล่า? ในบทความนี้เราจะมาดูกันว่าจริงๆแล้วเราควรจะเก็บรูปยังไง? และ ทำไม?

    Design Patterns

    หลักในการออกแบบซอฟต์แวร์โดยการนำ Design Patterns มาประยุกต์ใช้

    🤨 Design Patterns คือไรหว่า ?

    เวลาที่เราเจอ ปัญหา ในการออกแบบโค้ดนั้น เชื่อไหมว่าส่วนใหญ่มันจะเป็นปัญหาเรื่องเดิมๆที่เหล่า Programmer ทั่วโลกก็เจอกัน ดังนั้นมันเลยมีกลุ่มคนที่คิด วิธีการแก้ปัญหาที่เรามักจะเจอในการออกแบบซอฟต์แวร์ เอาไว้ โดยเรียกวิธีการแก้ปัญหาต่างๆว่า Design Patterns นั่นเอง ดังนั้นในรอบนี้เราจะทำความเข้าใจ หัวใจ และ ข้อดีข้อเสีย พร้อมกับการทำ Good practices ต่างๆในการออกแบบโดยใช้ Object-Oriented Programming (OOP) ไปพร้อมๆกันเลย

    ดังนั้นโดยสรุป Design Patterns คือ แนวคิด

    public class Envelope { }
    public class Letter { }
    public class Mailman { }
    public class Envelope
    {
       public string Sender;
       public string Receiver;
       public string Destination;
    }
    
    public class Letter
    {
       public string Title;
       public string Content;
    }
    
    public class Mailman
    {
       public void CollectAnEnvelope(Envelope env)
       {
          // ...
       }
    }
    public class Envelope
    {
       public string Sender;
       public string Receiver;
       public string Destination;
    
       public void Enclose(Letter letter)
       {
          // ...
       }
    }
    public class Envelope { }
    public class Envelope
    {
       public string Size;
       public string PaperMaterial;
       public string Pattern;
    }
    selecte * from Users where จังหวัด = 'กรุงเทพ';
    CREATE INDEX idx_province ON Users (จังหวัด);
    แม้ว่า RISC จะมีวงจรที่น้อยกว่า แต่มันก็สามารถทำงานต่างๆได้แบบที่ CISC ทำนะ เพียงแต่มันต้องนำวงจรที่มันมี ไปใช้ต่อๆกันหลายๆรอบถึงจะได้คำตอบ ซึ่งข้อดีของ RISC ก็คือ มันจะใช้ไฟน้อยกว่า ดังนั้นเขาเลยนิยมใช้ตัวประมวลผลแบบ RISC เอาไปทำเป็นอุปกรณ์พกพายังไงล่ะ หรือชื่อหนึ่งที่เรามักจะคุ้นเคยนั่นก็คือ สถาปัตยกรรมเออาร์เอ็ม (ARM architecture) งุย
    Microprocessor
    (P)
    (NP)
    Time complexity
    (ZPP)
    (RP)
    (BPP)
    (BQP)
    👦 Security พื้นฐาน
    วงจรการบวกแบบดิบๆ เห็นแล้วคิดถึงสมัยเรียนมหาลัยปี 1 จุง
    ถ้าต้องทำจริงๆ เจ้าคลาสลูกที่กำลังจะสร้างมันจะต้อง มีความแตกต่างจากคลาสแม่ และความแตกต่างนั้นจะต้อง ไม่เป็นการทำลายหัวใจหลักของคลาสแม่ เด็ดขาด เช่น กลุ่มของรถยนต์ดีเซล เราไม่สามารถเอา รถใช้ไฟฟ้า มาเป็นคลาสลูกได้ เพราะมันทำลายหัวใจในการ ใช้น้ำมัน ของคลาสแม่นั่นเอง
    ปัญหาที่ใหญ่ที่สุดในการทำซอฟต์แวร์
    Wikipedia
    https://en.wikipedia.org/wiki/Composition_over_inheritance
    (พวก members และ methods ต่างๆยังมีเหมือนเดิมนะ ขอเขียนย่อๆก็พอ)
    แนะนำให้อ่าน สำหรับใครที่ยังออกแบบ database ไม่เป็น ยัง งงๆ อยู่ว่าตารางนี้ควรจะเก็บอะไรดี หรือ Normalization คืออะไร? ความรู้ส่งคืนครูหมดแล้ว ก็สามารถไปศึกษาต่อได้จากลิงค์นี้เบยครัช 👶 บทสรุปฐานข้อมูล

    🤔 เก็บรูปในฐานข้อมูลผิดตรงหนาย ?

    ตามที่เกริ่นไปด้านบนว่า มันก็เก็บได้นะ แต่ปรกติทั่วโลกเขาไม่เก็บไว้ในนั้นกัน ส่วนสาเหตุหลักๆมาจาก 2 เรื่องตามด้านล่างเบย

    💳 ค่าใช้จ่าย

    ถ้าใครที่เคยจ่ายค่า database ด้วยตัวเองก็น่าจะพอทราบว่า ขนาดความใหญ่ของฐานข้อมูล ก็เป็นหนึ่งในปัจจัยที่ราคาจะถูกลงหรือแพงขึ้นด้วย ดังนั้นยิ่งเราทำให้ข้อมูลเราเล็กได้มากเท่าไหร่ เราก็จะประหยัดได้มากขึ้นเท่านั้นนั่นเอง

    ❓ มีน้องๆ developer หลายคนสงสัยกันต่อว่า แต่บางทีไฟล์รูปที่เก็บมันก็เล็กๆแค่ไม่มี 10KB เองนะ มันจะทำให้ฐานข้อมูลโตได้ยังไงอ่ะ ?

    จากคำถามด้านบนผมลองเปรียบเทียบให้ดูง่ายๆแบบนี้ละกัน โดยสมมุติว่าผมต้องการจะเก็บรูปด้านล่างนี้เข้าฐานข้อมูล ซึ่งมันมีขนาดแค่ 29.6KB เพียงเท่านั้นเอง

    ขนาดไฟล์ 29.6 KB

    ซึ่งก็จริงว่ามันก็ดูเหมือนจะไม่ได้ใหญ่โตอะไรเท่าไหร่เลยชิมิ? งั้นคราวนี้ผมจะลองสร้างไฟล์ text ที่มีขนาด 29.6KB ดูบ้างนะ ซึ่งมันก็จะประมาณรูปด้านล่างนี่แหละ ซึ่งลองสังเกต scroll bar ดูนะว่ามันยาวแค่ไหน

    ขนาดไฟล์ 29.6 KB หรือราวๆ 28,951 ตัวอักษร

    สำหรับใครที่ไม่จุใจอยากเบิกเนตรดูเต็มตาก็โหลดไฟล์ด้านล่างไปดูก็ได้

    จากที่ว่ามาไฟล์ขนาด 29 KB ดูเหมือนว่ามันจะเล็กๆเอง แต่เมื่อเทียบกับปริมาณข้อมูลตัวอักษรแล้วเราจะพบว่า ตัวอักษรมันมีขนาดเล็กกว่ามากเบย ซึ่งจากรูปด้านบนผมว่าเอาไปเขียนหนังสือจบ 1 บทแน่ๆเลย

    เกร็ดความรู้ ตัวอักษรที่เราพิมพ์ๆกันอยู่นั้นมีขนาด 1 Byte เพียงเท่านั้นเอง

    แล้วลองคิดดูว่าถ้าเราต้องเก็บไฟล์รูปเข้าไปในฐานข้อมูลสำหรับหรับทุก record เลยมันจะเกิดอะไรขึ้น ? เอาง่ายๆนะแค่เราเก็บรูป Profile (29.6KB) ในตารางผู้ใช้ แล้วเรามีคนใช้งานระบบเราซัก 1,000 คน พื้นที่ในฐานข้อมูลเราก็ใหญ่ราวๆ 30 MB ได้แล้ว

    แต่ในทางกลับกันถ้าเราเก็บข้อมูลเป็น text อย่างเดียว ผมเชื่อว่ามันเก็บข้อมูลผู้ใช้ได้น่าจะราวๆ 5 แสนถึง 1 ล้าน คนแน่นอน (ขึ้นอยู่กับเก็บอะไรบ้าง)

    มุมชวนคิด เพียงแค่เราเก็บรูปโปรไฟล์ของผู้ใช้ 1,000 คนลงในฐานข้อมูล เราก็จะต้องจ่ายเงินเท่ากับเรามีผู้ใช้ 5 แสนถึง 1 ล้านคนละ (แพงขึ้น 100~1,000 เท่า+) รูปโปรไฟล์มันสำคัญขนาดนั้นเลยเหรอ? และถ้าเรามีผู้ใช้ 1 ล้านคนล่ะ ? (มันก็จะโตขึ้น 30 GB เลยนะ!! นี่มันเก็บหนังได้กี่เรื่องฟร๊าา 😱)

    ลองมาดูราคาที่แท้จริงกันดีกว่า สมมุติว่าเรามีฐานข้อมูลขนาด 100 GB เราจะต้องจ่ายสูงสุดปรมาณ $ 25 USD ตามรูปด้านล่าง

    💨 Performance

    ทำไมการเก็บไฟล์รูปลงฐานข้อมูลถึงส่งผลให้ ประสิทธิภาพตกลง นะเหรอ? นั่นก็เพราะว่าตัวฐานข้อมูล (ส่วนใหญ่) มันถูกออกแบบมาให้ทำงานกับข้อมูลที่เป็น Simple type ยังไงล่ะ เช่น ตัวอักษร, ตัวเลข, true/false, วันที่และเวลา ไรพวกนี้ ซึ่งไฟล์รูปที่เก็บอยู่ใน database มันก็ทำงานด้วยได้นะ แต่ข้อมูลพวกนั้นมันจะไม่รู้เรื่องเพราะไฟล์รูปมันอยู่ในรูปแบบของ bytes ยังไงล่ะ และมันก็จะเป็นภาระเวลาที่เราดึงข้อมูลออกมาใช้งานด้วย เช่น แค่จะไปดึงข้อมูลผู้ใช้ออกมา มันก็ต้องไปดึงข้อมูลรูปพวกนั้นขึ้นมาอัดไว้ใน Memory ซึ่ง RAM มันมีจำกัดและราคาแพงม๊วกกกกกกก แถมยังก่อให้เกิดปัญหาเวลาที่เราส่งข้อมูลกลับไปให้ client ด้วย เพราะมันจะเปลือง network bandwidth อีกด้วย ลองดูราคาด้านล่างเอาละกัน หรือจะไปดูราคาเต็มๆของเซิฟเวอร์ทั้งหมดได้ที่ลิงค์นี้ Microsoft - VM Pricing

    มันน่าจะอยู่หัวข้อค่าใช้จ่ายเน๊อะ แต่ขี้เกียจย้ายละ

    แนะนำให้อ่าน แนวคิดพื้นฐานในการทำงานกับ database ที่เหล่าโปรแกรมเมอร์บ้านเราไม่ค่อยรู้ ไปอ่านแล้วทำความเข้าใจได้จากบทความนี้ เพราะมันโคตรสำคัญ ไม่งั้นต่อให้ทำทุกอย่างดีแค่ไหน แต่เรื่องนี้เรื่องก็ทำให้ระบบอืดจนเต่าแซงหน้าได้ หัวใจที่สำคัญที่สุดของฐานข้อมูล (🤔 ความรู้เบื้องต้นของฐานข้อมูลที่โปรแกรมเมอร์ 90% ไม่รู้)

    🤨 เคยอ่านเจออีกแบบนิ

    สำหรับแมวน้ำบางท่านอาจจะเคยอ่านเจอว่า Blob ในฐานข้อมูลสมัยนี้มันดีขึ้นมาแล้วนะ ไปเก็บในนั้นได้เหมือนเดิม แล้ว ซึ่ง ดช.แมวน้ำ ขอตอบแบบคร่าวๆว่า จริงครับสมัยนี้อะไรๆก็ดีกว่าสมัยก่อน เขาพัฒนาให้ดีขึ้นเยอะแล้ว แต่ถ้ามองในแง่ของการทำ Scaling เพื่อรองรับผู้ใช้ปริมาณมากๆแล้ว database ก็ยังเป็นคอขวดที่ใหญ่เป็นอันดับต้นๆของระบบอยู่ดี ดังนั้นในมุมของการทำให้ระบบรองรับการขยายตัวเพื่อรองรับผู้ใช้งานปริมาณมหาศาลแล้ว (Large Scalable Architectures) เราควรจะแยกของต่างๆออกจากกัน เพื่อให้เราสามารถ ลดขวด ให้ได้มากที่สุด นั่นเอง ซึ่งสิ่งที่ database ถนัดที่สุดก็คือการ เก็บข้อมูลที่เป็น simple type และ การประมวลผลคณิตรศาสตร์พื้นฐาน ไม่ใช่การเก็บไฟล์รูปแบบอื่นๆ ดังนั้นเราก็ควรจะเอาหน้าที่เก็บไฟล์รูปแบบอื่นๆไปเก็บไว้ใน service ที่เหมาะสมกับมันนั่นเอง

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

    https://docs.microsoft.com/en-us/azure/architecture/reference-architectures/app-service-web-app/scalable-web-app

    ข้อควรระวัง สมมุติว่าเราไม่กระจายงานออก แล้วไปพบว่ามันเป็นคอขวดภายหลัง วิธีแก้ปัญหาที่ง่ายที่สุดคือการ ขยายกำลังเครื่องในจุดที่เป็นคอขวด ซึ่งมันก็อาจจะแก้ได้ก็จริง แต่ประเด็นคือ เราอาจจะได้จ่ายเงินมากกว่าที่ควร เช่น ในจุดนั้นมันอาจจะมีงานที่รับผิดชอบ 10 เรื่อง แต่เป็นคอขวดแค่ 1 เรื่อง ซึ่งถ้าเราขยายกำลังเครื่อง มันก็จะต้องขยายทั้ง 10 เรื่องนั้นด้วย (เพราะเราไม่แยกงานมันออกเป็นแต่ละ services) เลยทำให้เราต้องจ่ายเงินเพิ่มให้กับสิ่งที่มันไม่ได้มีปัญหาด้วยนั่นเอง

    คำเตือน ทั้งหมดที่ว่ามาเป็นหลักในการทำ Large Scalable Architectures ซึ่งใช้ได้กับ Database ส่วนใหญ่เท่านั้น ซึ่งมันก็จะมี database ที่ออกแบบมาทำงานอีกประเภทนึงโดยเฉพาะเหมือนกัน ซึ่งตัวไหนเก่งอะไรยังไง ต้องให้แมวน้ำแต่ละท่านไปศึกษาตัวที่ใช้อยู่เองแล้วล่ะขอรับ

    😄 วิธีแก้ปัญหา

    ก็น่าจะพอเห็นภาพความบาปในการเก็บไฟล์รูปในฐานข้อมูลแล้วนะ วิธีการแก้กรรมหนักนี้คือการไปทำบุญล้างป่าช้า เพื่อนำเหล่าไฟล์ภาพออกจากฐานข้อมูลของเราไปเก็บไว้ในที่ชอบๆของมันนั่นเอง (ซี๊ดดดดดด กาวยี่ห้อใหม่ใช้ดี)

    ซึ่งจากที่ว่ามาทั้งหมดเราก็พอจะรู้แล้วว่า ฐานข้อมูลไม่ใช่ที่เก็บไฟล์รูป ดังนั้นเราก็ควรจะย้ายไฟล์รูปทั้งหมดไปไว้ในที่ๆสมควรของมันนั่นเอง และ ฐานข้อมูลควรจะเก็บข้อมูลที่เป็น Simple type ดังนั้นเราก็แค่เก็บ URL ของไฟล์รูปพวกนั้นก็เป็นอันเสร็จสิ้นแล้วนั่นเอง

    🤔 แล้วจะเก็บรูปไว้ที่ไหน ?

    จะเก็บไว้ที่ไหนก็แล้วแต่ความสะดวกของแมวน้ำแต่ละตัว เช่น จะเก็บไว้ในเซิฟเวอร์ตัวเอง หรือ จะเอาไปไว้พวกเว็บฝากไฟล์ก็ได้ ขอแค่เราได้ URL ที่สามารถเข้าถึงได้ก็พอ (ส่วนเรื่อง privacy ก็จัดการกันเอาเองนะ)

    ถ้าปิดจบแบบด้านบนก็ดูเหมือนจะใจร้ายไปหน่อยเอาเป็นแบบนี้ละกัน โดยปรกติ ดช.แมวน้ำ จะเก็บไฟล์ไว้ใน Cloud Service เพราะมันถูกม๊วก และสามารถควบคุมสิทธิ์และ privacy ได้แบบถึงลูกถึงคนเลย ชนิดที่ว่า Hack ไม่เข้าแน่นอน จะหลุดก็ต่อเมื่อเราเป็นคนตั้งค่าผิดกับมือเอง ส่วนเรื่องราคาก็ตามรูปด้านล่าง สมมุติว่าเก็บไฟล์ 100 GB ไว้ทั้งเดือนก็จ่ายแค่ $2 USD (60-70 บาท) เท่านั้นเอง

    เก็บหนัง ... ได้หลายเรื่องเลย

    แต่ถ้าเป็นไฟล์ที่นานๆๆๆครั้งจะมาเปิดดู ก็จะยิ่งถูกลงไปกว่านั้นได้อีก 100 GB จ่ายแค่ $0.2 USD (6~7 บาท) ต่อเดือนเท่านั้นเอง ตามรูปด้านล่างเบย

    นี่มันถูกกว่าซื้อ HDD เสียอีก

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

    🎯 สรุป

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

    แนะนำให้อ่าน ถ้าสงสัยว่าทำไมแอพมันช้าลองไปทำความเข้าใจเรื่อง คอขวด ของระบบจากบทความด้านล่างนี้ดู แล้วจะเข้าใจว่าผองเพื่อนของความช้านั้น เกิดจากอะไรได้บ้างครัช 👦 Bottlenecks of Software

    30KB
    29-6KB.txt
    Open
    ไฟล์ขนาด 29.6 KB ยาวขนาดไหน ?
    ในการแก้ปัญหาของการออกแบบซอฟต์แวร์ ที่เรามักจะเจอบ่อยๆนั่นเอง

    เกร็ดความรู้ กลุ่มคนที่ประกาศให้โลกได้รู้จักคำว่า Design Patterns นั้นมีด้วยกัน 4 คน ดังนั้นเรามักจะได้ยินคำว่า Gang of Four (GoF) เมื่อพูดถึงเรื่องนี้ด้วย โดยพวกเขาเขียนหนังสือที่ชื่อว่า Design Patterns: Elements of Reusable Object-Oriented Software ขึ้นมา ซึ่งภายในหนังสือนั้นอธิบายถึง Design Patterns 23 ตัว ที่เรากำลังจะได้เรียนรู้จักคอร์สนี้นั่นเอง

    🤨 ทำไมต้องเรียนตัวนี้ ?

    ผมมี 3 คำตอบที่น่าจะหนักแน่นพอให้เราสละเวลาทำความเข้าใจเรื่อง Design Patterns ต่างๆตามนี้

    1. ได้เรียนรู้การออกแบบ - เพราะ Design Patterns แต่ละตัวจะสอนการออกแบบให้รู้ถึง ข้อดี/ข้อเสีย ของมัน และแบบอื่นๆไปในตัว ดังนั้นมันเลยเป็นบทเรียนที่เหมาะกับ คนที่กำลังศึกษาการออกแบบ เพราะมันจะช่วยเปิดโลกให้เห็นหลักในการออกแบบ และ Practices หลายๆอย่างรวมอยู่ในนั้นเลย

    2. ประหยัดเวลา - เพราะมันคือ บทสรุปในการแก้ปัญหา ที่ทั่วโลกนิยมและรู้จักกัน ดังนั้นเราไปเจอปัญหาแบบเดียวกับเขา เราก็สามารถ ลอกคำตอบ ที่เขาคิดไว้แล้วไปใช้ได้เลยนั่นเอง

    3. มันเป็นคำศัพท์ - เพราะเวลาที่เราคุยกับ Developer คนอื่นๆว่าเรากำลังเจอปัญหาอะไรอยู่ หรือจะแก้มันยังไง บางทีแค่พูดชื่อของ Design Patterns ก็อาจจะทำให้ทุกคนเข้าใจสถานะการณ์หรือคำตอบได้เร็วขึ้น เลยทำให้มันเป็นหนึ่งใน คำศัพท์ ที่ช่วยให้พวก Developer เข้าใจกันได้มากขึ้นนั่นเอง

    😈 ข้อควรระวัง

    แม้ว่า Design Patterns จะเป็นเหมือนบทสรุปในการแก้ปัญหา ที่รอให้เราไปลอกคำตอบเขามาก็ใช้ได้เลย แต่อย่าลืมว่า ของฟรีและดีไม่มีอยู่จริง เพราะทุก Design Patterns นั้นจะมีข้อเสียที่ตามมากับมันด้วยเสมอ ดังนั้น อย่าเมากาว ตะบี้ตะบันเอา pattern ไปใช้กับงานเสมอ เราต้อง ชั่งน้ำหนักว่ามันคุ้มไหม ที่เราจะนำ pattern แต่ละตัวมาใช้นั่นเอง

    คำเตือน การนำ Design Pattern ไปใช้ไม่ใช่เรื่องเท่ เพราะมันมี Cost (memory, processing overhead & complexity) ของมันค่อนข้างสูง ดังนั้นก่อนใช้ให้ ชั่งน้ำหนัก ข้อดี/ข้อเสีย ให้ดีก่อน ไม่งั้นโค้ดจะทำงานได้แต่ maintenance ยากขึ้นโดยใช่เหตุ ดังนั้นอย่าเมากาวแล้วตะบี้ตะบันเอา pattern ไปใช้เลยตลอดเวลา ซึ่ง pattern แต่ละตัวมันต้องอาศัยประสบการณ์มาถึงระดับหนึ่ง มันถึงจะเข้าใจข้อดีข้อเสียของมันได้จริงๆเท่านั้น (แมวน้ำได้เตือนแล้วนะ)

    iconka.com

    Master of Design จะไม่ใช้ Design Patterns จนกว่ามันจะจำเป็นจริงๆเท่านั้น

    ❤️ แนวคิดในการศึกษาเรื่องนี้

    หัวใจหลักในการศึกษา Design Patterns ต่างๆนั้น (ส่วนตัวของผม) คือ

    เรียนรู้หลักปฎิบัติในการออกแบบ - เพราะ patterns ทุกตัวมันจะบอกข้อเสียของการออกแบบ Object-Oriented ที่ไม่ดี และวิธีการออกแบบที่ควรจะเป็น หรือที่เราเรียกว่า Best Practices เอาไว้ ซึ่งเจ้าสิ่งนั้นต่างหากที่มันเป็นสิ่งที่ Developer ควรที่จะศึกษา เพื่อที่จะได้นำเป็นเป็น ทางเลือก ในการแก้ปัญหาหน้างานของตัวเอง

    ส่วนรายละเอียดของ Design Patterns ทั้ง 23 ตัวถือว่าเป็นความรู้โบนัสที่ รู้ติดตัวก็ดีไม่รู้ก็ได้ เพราะสิ่งที่เราควรศึกษาจริงๆคือ แนวคิด ต่างหาก เพราะผมคิดว่า Software มันเป็นงานที่ต้องออกแบบเฉพาะเรื่องของมันนั่นเอง

    💎 กลุ่มของ Patterns

    Design Patterns พื้นฐานทั้ง 23 ตัวนั้น มันถูก แบ่งออกเป็น 3 กลุ่มตามลักษณะในการแก้ไขปัญหาของมัน นั่นเอง ดังนั้นเวลาที่เราเจอปัญหา แล้วเราจะเลือก Design Patterns มาใช้นั้น เราก็จะดูว่า ปัญหา ของเรานั้นมันเป็นกลุ่มไหน แล้วค่อยเลือกต่อว่า Patterns ในกลุ่มนั้นๆ มันแก้ปัญหาเรื่องอะไร แล้วเราค่อยหยิบมันไปแก้ไขปัญหาของเรานั่นเอง ดังนั้นเรามาเรียนรู้ว่าแต่ละกลุ่มจะแก้ปัญหาเรื่องอะไรกันดีกว่า

    🔥 Creational Patterns

    กลุ่มนี้จะช่วยให้ developer ไม่ต้องเสียเวลาในการ สร้าง object ซึ่งแม้ว่ามันจะสร้างง่ายๆด้วยคำสั่ง new เท่านั้นก็ตาม เพราะการสร้าง object ด้วยตัวเองมันคือ Hardcode แบบหนึ่งนั่นเอง ดังนั้นถ้าเรามีปัญหาเรื่อง การสร้าง object ให้เรามองมาที่ Patterns ต่างๆที่อยู่ในกลุ่มนี้ได้เลย

    Hardcode คือการทำอะไรบางอย่างลงไปในโค้ดของเรา ที่เวลาเราแก้ไขเจ้าสิ่งนั้นเราจะต้องทำการ Compile ตัวโค้ดเราใหม่ทั้งหมด ไม่งั้นจะแก้ไขมันไม่ได้

    🔥 Structural Patterns

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

    🔥 Behavioral patterns

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

    🧭 เนื้อหาทั้งหมดของคอร์สนี้

    คอร์สนี้กำลังค่อยๆเขียนอยู่ ใครที่ไม่อยากพลาดอัพเดทก็เข้าไปกดติดตามที่ลิงค์นี้ Mr.Saladpuk ได้เลย ส่วนใครที่อยากศึกษา pattern ตัวไหนล่วงหน้าก็ไปอ่านบทความเก่าได้ที่ลิงค์นี้ 🤴 Design Patterns (อ่านแล้วเมากาวไม่รู้ด้วยนะ)

    🤰 Creational Patterns

    🧱 Structural Patterns

    • Bridge Pattern

    • Composite pattern

    • Decorator Pattern

    • Facade Pattern

    • Flyweight pattern

    🦈 Behavioral patterns

    • Chain of Responsibility Pattern

    • Command Pattern

    • Interpreter pattern

    • Iterator Pattern

    • Mediator Pattern

    • Memento Pattern

    • Observer Pattern

    • State Pattern

    • Strategy Pattern

    • Template Method Pattern

    • Visitor Pattern

    🤨 คอร์สนี้ต่างจากอันเดิม ?

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

    🏭Factory Method
    🏭Abstract Factory
    ☝️ Singleton Pattern
    🏗️ Builder Pattern
    🎎Prototype Pattern
    🔌Adapter Pattern
    📪Proxy Pattern

    วิธี (Algorithm)

    จำนวนครั้งที่ทำ

    ตัวอย่าง

    บวกซ้ำ

    ขึ้นอยู่กับตัวคูณ

    15 x 5,000 ต้องทำซ้ำ 5,000 ครั้ง

    คูณทีละตำแหน่ง

    (หลักของตัวคูณ x 2) -1

    15 x 5,000 ต้องทำซ้ำ (4x2)-1 = 7 ครั้ง

    Round-off error
    👽 Algorithm P & NP
    ห.ร.ม. (หารร่วมมาก)
    Euclid's algorithm
    👶 สิ่งที่คนเขียนโค้ดมักเข้าใจผิด
    👽 Algorithm P & NP
    https://www.bigocheatsheet.com
    https://www.bigocheatsheet.com
    https://www.scientecheasy.com/2018/06/encapsulation-in-java-real-time-examples-advantages.html
    🧠 Challenges
    Saladpuk Fanclub
    กรณีโชคร้ายก็เสียไป 3 ครั้งละ เลยต้องไปจบในการชั่งครั้งที่ 4
    หากรูปผิดฝากบอกด้วยนะ ตัดต่อจนเมาไปหมดละ 😵

    Inheritance & Polymorphism

    หลังจากที่เข้าใจหลักการของ Inheritance กับ Polymorphism กันไปเรียบร้อยแล้ว ดังนั้นเรามาสรุปความเข้าใจเจ้า 2 ตัวนี้ก่อนว่ามันเกี่ยวเนื่องกันยังไง

    🔥 Inheritance

    เป็นการนำ Model ที่มีอยู่แล้วไปสร้างรูปแบบการทำงานใหม่ๆได้

    🔥 Polymorphism

    เป็นการรองรับให้ object สามารถอยู่ได้หลากหลายรูปแบบ และทำงานกับ object ที่แท้จริงของมัน

    💖 Abstraction + Encapsulation

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

    ข้อควรระวัง

    • อย่าใช้ Inheritance เพราะเราขี้เกียจเขียนโค้ดซ้ำๆ เพราะ Inheritance มันเป็นความสัมพันธ์เป็นแบบ Is A นั่นเอง (มันไม่ดียังไงตัวอย่างอยู่ด้านล่าง)

    👎 Bad Design

    การทำ Inheritance ที่ไม่ถูก เช่น เราทำแอพขายหนังสือและแมวโดยมีระบบสมาชิกด้วย เลยทำให้เรามี Model 3 ตัวนั่นคือ หนังสือ, แมว และ สมาชิก ซึ่งมีโค้ดเป็นแบบนี้

    หลายครั้งที่เราจะพบว่า Developer จะมองว่า มันมีของที่เหมือนกัน ดังนั้นก็ใช้ Inheritance ซิมันจะได้ลดโค้ดที่ซ้ำกันออก กลายเป็นแบบนี้

    ซึ่งมันก็ทำงานได้นะ ลดโค้ดที่ซ้ำกันได้ด้วยแต่ มันใช้ของผิดวัตถุประสงค์ (เฟร้ยยยย) มันเหมือนกับเรากำลังเอาตะเกียบไปกินไอติม ซึ่งดูผิวเผินก็คีบกินได้ไม่มีไร แต่ถ้าปล่อยทำแบบนี้ต่อไปเรื่อยๆ เมื่อไหร่ที่ไอติมละลายเราก็จะกินต่อไม่ได้นั่นเอง เมื่อเทียบกลับมาที่โค้ดด้านบน มันก็จะทำงานได้ แต่ถ้าปล่อยไปเรื่อยๆมีการใช้ของแบบนี้ต่อไปเรื่อยๆเดี๋ยวมันจะมีปัญหาตามมาอีกที เพราะในกรนี้นี้เราใช้ inheritance โดยไม่ได้คำนึงถึงความสัมพันธ์แบบ IS A ยังไงล่ะ

    🤨 ยังไม่เข้าใจอ่ะ การใช้ความสัมพันธ์แบบ IS A แบบไม่ถูกมันจะมีปัญหาไร?

    IS A ถ้าแปลเป็นภาษาไทยมันแปลได้ว่า "มันเป็น" นั่นเอง ซึ่งลองเอา Model ตามโค้ดด้านบนมาดูนะว่ามันฟังแล้วมันแปลกๆหรือเปล่า

    1. Book IS A ProductInformation - หนังสือเป็นข้อมูลสินค้าประเภทหนึ่ง

    2. Cat IS A ProductInformation - แมวเป็นข้อมูลสินค้าประเภทหนึ่ง

    3. Member IS A ProductInformation - สมาชิกเป็นข้อมูลสินค้าประเภทหนึ่ง

    จากด้านบนข้อ 1 กับ 2 อ่านแล้วยังพอเข้าใจได้ เพราะเราขาย หนังสือ และ ขายแมว ดังนั้นทั้ง 2 อย่างนี้ยังพอเข้าใจได้ แต่ข้อ 3 สมาชิกมันไม่ใช่ข้อมูลสินค้าแน่ๆ แต่เนื่องจากเราเอา Inheritance มาใช้ มันเลยทำให้มันเกิดความสัมพันธ์แบบนั้นเกิดขึ้น ดังนั้นในอนาคตมันจะเกิดปัญหาขึ้นโดยไม่ตั้งใจได้

    เช่น เราจะสร้าง Method เอาไว้คิดเงินละว่าสินค้าที่เลือกเข้ามาทั้งหมดราคาเท่าไหร่ ซึ่งเราก็อาจจะเขียนแบบง่ายๆออกมาเป็น

    เนื่องจากเราทำเป็น Inheritance ดังนั้นมันเลยได้ความสามารถของ Polymorphism เข้ามาด้วย เลยทำให้เราสามารถส่ง object ที่เป็น Book และ Cat เข้ามาใน method ตัวนี้ได้ ... แต่ก็อย่าลืมนะว่ามันก็ส่ง object Member เข้ามาคิดราคาได้ด้วยเหมือนกัน!!

    👍 Good Design

    จากตัวอย่างแอพขายหนังสือและแมวโดยมีระบบสมาชิกถ้าเราจะออกแบบให้ดีนั้นมันมีหลักการง่ายๆแค่

    มันมีเหตุผลอะไรที่ต้องใช้ Inheritance ไหม? ใช้แล้วได้ประโยชน์อะไร? จำเป็นจริงๆหรือเปล่า?

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

    แต่ถ้าได้คำตอบซึ่งมีเหตุผลรองรับที่ดีแล้วล่ะก็ ใช้ได้เลย แต่จงทำให้ถูกในมุมของความสัมพันธ์ด้วย เช่น ถ้าหนังสือและแมว มันเป็นสินค้าในร้าน เราจะให้มันมาจาก Base class ที่เป็น Product ก็ได้

    ส่วนเจ้าสมาชิกถ้ามันมี Model แค่ตัวนี้ตัวเดียวก็ปล่อยไปแบบเดิมก็ได้ อย่าไปทำอะไรที่มันซับซ้อนเลย

    แต่ถ้าเรามีสมาชิกหลายแบบ เช่น VIP หรืออะไรก็แล้วแต่ ให้เราถามคำถามเดิมต่อว่า มันมีเหตุผลอะไรที่ควรจะต้องทำ Inheritance ไหม? ซึ่งถ้าไม่มี ผมเสนอว่าทำตัวแปรมาคัดแยกประเภทเอาจะง่ายกว่า ไม่ซับซ้อนด้วย

    แต่ถ้าเรามีเหตุผลที่เพียงพอก็จะแตกกลุ่มออกมาอีกก็ได้

    สมมุติว่าเรามีเหตุผลที่ดีพอในการทำ Inheritance แล้วล่ะก็ อย่าลืมเอาความสัมพันธ์มันมาตรวจสอบดีๆอีกครั้งด้วยล่ะ ดังนั้นเราลองเอาแผนภาพ UML มาดูคร่าวๆกัน

    ลองตรวจความสัมพันธ์ของแต่ละกลุ่มดูว่ามันเหมาะสมหรือยัง

    กลุ่มสินค้า

    1. หนังสือ เป็น สินค้าประเภทหนึ่ง

    2. แมว เป็น สินค้าประเภทหนึ่ง

    กลุ่มสมาชิก

    1. สมาชิกธรรมดา เป็น สมาชิกประเภทหนึ่ง

    2. สมาชิก VIP เป็น สมาชิกประเภทหนึ่ง

    เมื่อลองอ่านด้านบนแล้วก็น่าจะไม่มีอะไรผิดปรกติแล้วใช่ไหม เพียงเท่านี้เราก็สามารถนำไปใช้งานได้อย่างสบายใจในระดับหนึ่งละ

    จะทำ Inheritance ให้มองย้อนถึง IS A Relationship ก่อนทำเสมอ และ เหตุผลที่มีน้ำหนักคุ้มที่จะทำ

    🎯 บทสรุป

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

    คำเตือน การใช้ Inheritance โดยไม่คำนึงถึงความสัมพันธ์แบบ IS A มันจะส่งผลร้ายเป็น bug ที่ไม่ส่งเสียงในอนาคต รอวันที่เราเผลอลืมของที่สร้างไว้ จนวันหนึ่งมีคนในทีมไปเหยียบกับระเบิดเข้า ก็จะส่งสมาชิกเข้าไปเป็นสินค้าเพื่อคำนวณราคาออกมา ซึ่งมันอาจจะทำงานได้ หรือ ไม่ได้ไม่รู้ล่ะ แต่ในมุมของการออกแบบและการทำงานจริง มันไม่ถูกทั้งคู่ ดังนั้นมันจะทำให้โปรแกรมของเรามีของแปลกๆอยู่ในนั้น เหมือนกับเวลาเราเห็นคนเอาตะเกียบไปกินไอติมนั่นแหละ

    แนะนำให้อ่าน หลักในการออกแบบขั้นพื้นฐาน หรือที่เราเรียกว่า SOLID Design เพื่อนๆสามารถไปศึกษาอ่านได้จากลิงค์ด้านล่างนี้เบย

    ยาต้านโควิด

    โจทย์สอบสัมภาษณ์เข้า Saladpuk

    🥳 โจทย์

    ทรัพยากรทั้งโลกสามารถผลิตยาต้านโควิดได้แค่ 1,000 ขวดเท่านั้น แต่ก็ดันมีคนร้ายแอบผสมยาพิษลงไป 1 ขวด ซึ่งยาพิษตัวนี้ตรวจด้วยวิธีการใดๆก็ไม่พบ และคนที่กินก็จะตายในอีก 23 ชั่วโมงหลังจากที่กินเข้าไป ประจวบกับคนสำคัญทั่วโลกต้องได้รับยาตัวนี้ภายใน 24 ชั่วโมงไม่งั้นจะไม่สามารถรักษาชีวิตพวกเขาไว้ได้ ซึ่งทั่วทั้งโลกมีอาสาสมัครเพียงแค่ 10 คนเท่านั้น ที่พร้อมจะกินยาเหล่านั้นแม้รู้ว่าอาจจะตายก็ตาม

    • ในขวดมียาหลายเม็ด ส่วนคนกินแค่เม็ดเดียวก็พอ

    • ทุกเม็ดในขวดยาพิษกินแล้วตายใน 23 ชม เหมือนกัน

    • จะให้คนกินกี่เม็ดก็ได้ กี่ขวดก็ได้ กินขวดซ้ำกันก็ได้

    • จะใช้อาสาสมัครทุกคน หรือ แค่บางคนก็ได้ แต่ต้องไม่เกิน 10 คนที่กำหนดไว้

    • ถ้าจะช่วยผู้ป่วยได้ครบต้องมียาเหลือเกิน 900 กระปุก

    เราจะทำยังไงเพื่อรักษาชีวิตคนให้ได้มากที่สุด โดยที่เสียยาไปน้อยที่สุด?

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

    🙇‍♂️ กราบขออภัย

    โจทย์ข้อนี้พอเอามาให้โปรแกรมเมอร์เล่นแล้วพบว่ามี bug เยอะมาก เพราะตัวโจทย์ไม่ได้เคลียเรื่องเวลา มันต้องเอาไปใช้จริงหรือเปล่า บลาๆ ดังนั้นกระผมขอทราบขออภัยที่ไม่ได้ระวังในเรื่องนี้ก่อนครับ 🙇‍♂️ ดังนั้นแมวน้ำจะขอเฉลยในรูปแบบต่างๆตามด้านล่างนี้ละกันนะ

    🤠 วิธีคิด

    จำนวนผู้อาสาสมัคร 10 คนนั้น ถ้าเราวางแผนดีๆก็จะสามารถระบุขวดยาพิษได้ 100% เลย โดยการ สลับกันกินคนละครึ่ง ตามตารางด้านล่างนี้

    จากตารางด้านบนจะทำให้เรารู้ว่า ถ้าใช้คน 1 คน เราจะเสียขวดยาแค่ 500 ขวด แต่ถ้าใช้ 2 คนช่วยกันจะเสียขวดย่แค่ 250 ขวด ไปเรื่อยๆจนอาสาสมัคร 10 คนช่วยกันก็จะเสียขวดยาแค่เพียง 1 ขวดเท่านั้น (เศษปัดขึ้นทุกกรณี)

    🤔 กินยาไง? (แบบมนุษย์เข้าใจ)

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

    ถัดมาให้อาสาคนแรกกินยาไปครึ่งหนึ่ง (1000 / 2 = 500 ขวด) ก็จะได้ตามรูปด้านล่าง

    จากรูปด้านบนถ้าโชคดี/โชคร้าย เราก็จะมั่นใจได้ว่ามียาที่ปลอดภัยแน่ๆ 500 ขวด และมีอีก 500 ขวดที่ยังมั่นใจไม่ได้ ดังนั้นเราก็จะให้อาสาสมัครรายที่ 2 ไปช่วยกิน ซึ่งเราก็จะให้เขากิน 500 ขวดเหมือนกัน แต่จะให้เว้นช่วงเป็นครึ่งหนึ่งของคนแรก (500 /2 = 250) ดังนั้นอาสาคนนี้จะกินยา 250 ขวดแล้วเว้นไว้ 250 ขวด สลับไปเรื่อยๆ ตามรูปด้านล่าง

    จากรูปด้านบนถ้าโชคดีไม่มีคนตายเลย เราก็จะมั่นใจได้ว่ายาที่อาสาทั้งสองกิน 750 ขวดนั้นปลอดภัย โดยเทียบกับตารางด้านล่างนี้

    ดังนั้นจากที่ว่ามาเราก็สามารถทำแบบนี้ให้กับอาสามัครไปเรื่อยๆ ให้ทุกคนกินเว้นระยะกันไปแบบนี้เรื่อยๆก็จะทำให้เราได้ผลลัพท์ออกมาราวๆนี้

    ดังนั้นจากรูปด้านบนก็จะทำให้เรารู้ว่า กรณีที่ดีที่สุดคืออาสาหมายเลข 10 ตายคนเดียว และ กรณีที่เลวร้ายที่สุดก็อาจจะตายหมดก็ได้ 😥 แต่ไม่ว่ายังไงก็ตาม เราก็จะสามารถระบุขวดที่เป็นยาพิษได้ 100% นั่นเอง

    สาเหตุที่ใช้คำว่าอาจจะตายหมด เพราะขึ้นอยู่กับว่าเราจะให้อาสาสมัครกินขวดที่คร่อมกันยังไงนั่นเอง เพราะจำนวนคน 10 คนนั้นสามารถทำความน่าจะเป็นออกมาได้ทั้งหมด 2 ยกกำลัง 10 = 1,024 แบบนั่นเอง

    😎 กินยาไง? (ฉบับขาเดฟ)

    เราก็ให้อาสาสมัครทั้ง 10 คนมายืนตามตำแหน่งด้านล่าง

    ถัดมาเราก็ให้อาสาสมัครกินตามเงื่อนไขของตัวเองตามนี้

    อธิบายด้านบนนิด เราจะติดเลขไว้บนขวดยาไว้ตั้งแต่ 1~1000 และใส่ขวดยาเปล่าที่ติดเลข 0 ไว้ แล้วส่งไปให้อาสาสมัครทีละขวดตามลำดับ

    1. พอขวดยาขวดแรกที่เป็นเลข 0 มา ก็จะไม่มีใครกินเพราะทุกคนต้องปล่อยให้ขวดยาไหนผ่านเท่ากับตัวเลขที่อยู่ตรงหน้า

    2. ขวดถัดมาที่เป็นเลข 1 ไหลมาปุ๊ป ผู้อาสาคนแรกก็จะเปิดกินเพราะเขาปล่อยขวดยาไหลผ่านครบเงื่อนไขหนึ่งขวดแล้ว ส่วนคนอื่นๆยังไม่ครบก็จะไม่มีใครกิน

    3. ขวดถัดมาที่เป็นเลข 2 ไหลมาปุ๊ป ผู้อาสาคนแรกก็จะไม่กินเพราะกินครบแล้วต้องรอให้มันไหลผ่านไป แต่อาสาหมายเลขสองจะต้องกินเพราะเขาปล่อยขวดยาไหลผ่านไป 2 ขวดครบตามเงื่อนไขแล้ว

    พออธิบายมาถึงตรงนี้เหล่าขาเฟทั้งหลายก็น่าจะถึงบางอ้อนว่า การกินยาเขาไปนั้นก็มีโอกาสแค่ เป็น กับ ตาย เท่านั้น ซึ่งมันก็เหมือนกับ Binary ที่มีค่าเป็น 0 กับ 1 ดังนั้นสมมุติว่าอาสาสมัครหมายเลข 9 กับ 4 ตายเราก็จะรู้เลยว่าขวดยาพิษนั้นเป็นหมายเลข 264 นั่นเอง ตามรูปด้านล่าง

    วิธีกินยาแบบนี้กับแบบแรกจริงๆคือแบบเดียวกัน แต่เอามาอธิบายให้ต่างกันนิดหน่อยโดยการกลับด้านเจ๋ยๆ ไม่เชื่อลองเอาคนที่ 10 ของวิธีนี้ไปเทียบกับคนที่ 1 ของวิธีแรกจิ ซึ่งจะเห็นว่ามันไม่ต่างอะไรกันเลยนั่นเอง 😁

    🤪 Genius

    อย่างที่บอกว่าโจทย์นี้มี bug เรื่องเวลา ซึ่งถ้ายาพิษมันออกฤทธิ์แบบเที่ยงตรงระดับ millisecond นั่นหมายความว่า เราสามารถให้ผู้สมัครคนเดียวกินยาทั้ง 1,000 ขวด โดยเว้นระยะห่างกัน 0.0001 ms แล้วก็รอดู 23 ชั่วโมงให้หลังว่าเขาจะตาย ms ที่เท่าไหร่นั่นเอง 👏

    🎯 ข้อคิดที่ได้

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

    โจทย์ข้อนี้ถ้าทำพอผ่านก็ให้อาสาสมัคร 10 คนกินยาคนละ 100 ขวด ก็จะผ่านเงื่อนไขขั่นต่ำแล้วนั่นเอง แต่ถ้าจะรีดเน็นจริงๆแล้วคงต้องใช้เวลาคิดมากกว่านี้ ซึ่งถ้าต้องแลกกับเวลาเลี้ยงลูกเลี้ยงเมียมากเกินไป ก็เอาเวลาไปทำในส่วนอื่นจะเหมาะกว่านะ (เมียแอบกระซิบมา) 🤣

    Creational Patterns

    ตัวช่วยในการสร้าง object

    Best Practice Program to an interface and not to an implementation. หรือเราสามารถแปลง่ายๆได้ว่า การเขียนโค้ดต่างๆ ไม่ควรไปทำงานกับของระดับล่าง แต่จงทำงานกับระดับ abstraction เท่านั้น

    🤔 ทำไมต้องเป็นแบบนั้น ?

    ขออธิบายให้เห็นภาพก่อนว่า ในชีวิตจริงถ้าเราจะสร้างบ้านซักหลัง เราจะเดินไปบอก กรรมกร หรือ ผู้รับเหมา เพื่อให้เขาสร้างบ้านให้เรากัน? ... คำตอบก็คือผู้รับเหมายังไงล่ะ เพราะเขามีหน้าที่รับผิดชอบสร้างบ้านให้เรา ซึ่งเขาก็จะไปสั่งงานกรรมกรมาสร้างบ้านให้เราอีกทีหนึ่ง ซึ่งถ้ากรรมกพวกนั้นเกเร ผู้รับเหมาก็อาจจะเปลี่ยนกรรมกรก็ได้ ซึ่งก็ไม่เกี่ยวข้องอะไรกับเรานั่นเอง (Builder Patterns)

    กลับมาที่โค้ดกันบ้าง สมมุติว่าเราอยากจะสร้าง object ของ หมา มาใช้งานซักตัว ปรกติเราก็มักจะเขียนโค้ดออกมาราวๆนี้

    ซึ่งเจ้าโค้ดด้านบนก็ไม่ได้ผิดอะไรนะ แต่มันผิดหลักในการออกแบบ 😲 เพราะโค้ดของเราตอนนี้กำลังทำงานกับของระดับล่างอยู่ เพราะเราใช้คำสั่ง new ยังไงล่ะ และคลาส Dog มันก็เป็นการทำงานระดับล่าง หรือที่เราเรียกว่าระดับ Implementation แล้วยังไงล่ะ

    👎 ข้อเสียในการทำงานกับของระดับล่าง

    จากโค้ดด้านบน มันจะเป็นการผูกการทำงานของเราให้ต้องทำงานกับคลาส Dog เท่านั้น ไม่สามารถทำงานกับคลาสอื่นได้ ซึ่งถ้าเราอยากเปลี่ยนให้มันทำงานกับคลาสอื่นๆ เช่น คลาสแมว คลาสหมู ไรงี้ เราจะต้องเขียนโค้ดและ Compile มันใหม่ยกชุดเท่านั้น ซึ่งเราเรียกโค้ดลักษณะนี้ว่า Hardcode นั่นเอง

    ข้อแนะนำ การเขียนโค้ดโดยใช้คำสั่ง new ก็ไม่ได้ผิดอะไรร้ายแรงหรอก เพียงแค่โค้ดมันไม่ยืดหยุ่นต่อการเปลี่ยนแปลงเฉยๆ ซึ่งข้อดีของมันคือ Developer ทุกคนทำได้ง่าย และไม่ซับซ้อน

    👍 ข้อดีในการทำงานกับระดับ Abstraction

    แทนที่เราจะทำงานกับคลาสที่เป็นระดับการทำงานจริงๆ (Implementation) ให้เราเปลี่ยนมาทำงานกับของที่เป็น Abstraction ของมันจะดีกว่า เพราะเราจะสามารถเปลี่ยนไปทำงานกับคลาสอื่นๆที่เป็นตระกูลนั้นได้ทุกตัวเลย

    ข้อแนะนำ ความหมายของการ ทำงานกับระดับ Abstraction ไม่ได้หมายถึง abstract class นะ แต่เป็นการเหมารวมของที่เป็น ต้นแบบ หรือเรียกอีกแบบว่า Concept เช่น Abstract, Interface

    เพราะการโค้ดของเราจะมีลักษณะของการทำ Polymorphism อยู่ภายในตัวเองอยู่แล้ว ดังนั้นมันจะสามารถรองรับการทำงานอื่นๆในอนาคตได้ทันที โดยที่เราเขียนโค้ดเพียงแค่ครั้งเดียวเท่านั้น เช่น เมื่อเราเพิ่มคลาส สิงห์โต, วัว และ หมีเข้าไป โค้ดเดิมที่ทำงานกับ Animal ที่เป็น Abstraction ก็จะรองรับเจ้าคลาสใหม่ๆพวกนั้นทันที โดยที่เราไม่ต้องไปแก้โค้ดอะไรทั้งสิ้นเลยนั่นเอง ตามรูปด้านล่าง

    แนะนำให้อ่าน ใครจำเรื่อง Polymorphism ไม่ได้แล้วให้กดกลับไปทบทวนได้จากลิงค์นี้เลย

    😭 ใครช่วยแก้ปัญหานี้ได้บ้าง ?

    ในกลุ่มของ Creational Patterns นี้มีพระเอกหลายตัวเลยที่จะมาช่วยแก้ปัญหาในการสร้าง object ซึ่งแต่ละตัวนั้น มันจะเหมาะกับปัญหาแต่ละแบบ ดังนั้นไปดูว่าแต่ละตัวมันช่วยแก้ปัญหาอะไรเลยละกัน

    ช่วยสร้าง object ที่เหมาะสมกับสถานะการณ์ที่กำลังเป็นอยู่

    ช่วยสร้างกลุ่มของ object ที่มีความเกี่ยวข้องกัน

    ช่วยให้ object ตัวนั้นมีได้เพียงแค่ตัวเดียวเท่านั้น และใครๆสามารถเข้าถึงได้

    ช่วยสร้าง object ที่มีขั้นตอนในการสร้างอันวุ่นวายให้ง่ายขึ้น

    ****

    ช่วยในการก๊อปปี้ object หนึ่ง ออกไปเป็นอีก object ได้แบบง่ายๆ

    คำเตือน ไม่ควรนำ Design Patterns ไปใช้ โดยไม่ได้ชั่ง ผลดี/ผลเสีย ให้เรียบร้อยก่อน เพราะมันจะทำให้โค้ดเราซับซ้อนโดยไม่จำเป็น แล้วจะแก้ไขปรับปรุงอะไรต่างๆก็จะกลายเป็นเต่า แต่จงศึกษา Design Patterns เพื่อเรียนรู้หลักในการออกแบบ เพื่อจะได้รู้ว่าเมื่อไหร่ไม่ควรใช้ Pattern

    คอร์สนี้กำลังค่อยๆเขียนอยู่ ใครที่ไม่อยากพลาดอัพเดทก็เข้าไปกดติดตามที่ลิงค์ ได้เลย ส่วนใครที่อยากศึกษา pattern ตัวไหนล่วงหน้าก็ไปอ่านบทความเก่าได้ที่ลิงค์นี้ (อ่านแล้วเมากาวไม่รู้ด้วยนะ) + ในคอร์สนี้จะเริ่มอธิบาย Pattern แต่ละตัวจากกลุ่มนี้ก่อนนะครัช

    2019-09

    ✏️ ประวัติการอัพเดท

    สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

    // การร้อง 2 แบบ
    var quack1 = new Quack(); // ร้องแคว๊กๆ
    var quack2 = new Squeak(); // ร้องเสียงแหลม
    
    // การบิน 2 แบบ
    var fly1 = new CannotFly(); // บินไม่ได้
    var fly2 = new FlyWithWings(); // บินด้วยปีก
    
    // เป็ดหัวแดงที่ร้องแคว๊กๆ และ บินไม่ได้
    Duck readHead = new RedheadDuck(fly1, quack1);
    
    // เปลี่ยนพฤติกรรมให้มันสามารถบินได้ ระหว่าง Runtime
    readHead.ChangeFlyBehaviour(flt2);
    a = 0.2
    b = 0.1
    c = a + b
    public int GCD(int a, int b)
    {
        var x = Math.Max(a, b);
        var y = Math.Min(a, b);
        
        while(y != 0)
        {
            var remain = x % y;
            x = y;
            y = remain;
        }
        
        return x;
    }
    public class BankAccount
    {
        public string OwnerName;
        public double Balance;
    }
    static void Main(string[] args)
    {
        var acc = new BankAccount();
        acc.OwnerName = "Saladpuk";
        acc.Balance += 500;
    
        Console.WriteLine($"Account: {acc.OwnerName}, has THB {acc.Balance}.");
    }
    static void Main(string[] args)
    {
        var acc = new BankAccount();
        acc.OwnerName = "Saladpuk";
        acc.Balance += 500;
        acc.Balance -= 1200;
    
        Console.WriteLine($"Account: {acc.OwnerName}, has THB {acc.Balance}.");
    }
    public class BankAccount
    {
        public string OwnerName;
    
        // แก้มันเป็น private member เพราะเราไม่อยากให้คนอื่นเข้ามาแก้มันได้มั่วซั่ว
        private double balance;
    }
    public class BankAccount
    {
        public string OwnerName;
        private double balance;
    
        // ใครอยากจะดูยอดเงินก็เข้ามาดูผ่าน property ตัวนี้ ซึ่งมันดูได้อย่างเดียวแก้ไม่ได้
        public double Balance { get => balance; }
    }
    public class BankAccount
    {
        public string OwnerName;
        private double balance;
        public double Balance { get => balance; }
    
        // ช่องทางให้คนอื่นเข้ามาเพิ่มจำนวนเงินได้ โดยที่ไม่ทำให้ sensitive data มีปัญหา
        public void Deposit(double amount)
        {
            if (amount > 0)
            {
                balance += amount;
            }
        }
    }
    static void Main(string[] args)
    {
        var acc = new BankAccount();
        acc.OwnerName = "Saladpuk";
        acc.Deposit(500);
        acc.Deposit(-1200);
    
        Console.WriteLine($"Account: {acc.OwnerName}, has THB {acc.Balance}.");
    }
    var acc = new BankAccount();
    acc.OwnerName = "Saladpuk";
    acc.Deposit(500);
    30/09/2019
    • อัพเดทบทความ 👶 UML พื้นฐาน เรื่อง Use case Diagram

    29/09/2019

    • อัพเดทบทความ 👶 UML พื้นฐาน เรื่อง Sequence Diagram

    28/09/2019

    • อัพเดทบทความ 👶 UML พื้นฐาน เรื่อง Activity Diagram

    • อัพเดทบทความ 👶 UML พื้นฐาน เรื่อง Class Diagram

    • ตั้งคอร์สใหม่ 👶 UML พื้นฐาน

    26/09/2019

    • อัพเดทบทความ 👶 Docker ขั้นพื้นฐาน เรื่อง แชร์ Docker Image ที่สร้างไว้

    25/09/2019

    • อัพเดทบทความ 👶 Docker ขั้นพื้นฐาน เรื่อง Image and Container

    • ตั้งคอร์สใหม่ 👶 Docker ขั้นพื้นฐาน

    24/09/2019

    • อัพเดทบทความ 👶 Blockchain เรื่อง สร้าง Blockchain ใช้เองกัน !

    22/09/2019

    • อัพเดทบทความ 👶 Blockchain เรื่อง Consensus Algorithm คืออะไร ?

    • อัพเดทบทความ 👶 Blockchain เรื่อง Blockchain ทำงานยังไง ?

    21/09/2019

    • ตั้งคอร์สใหม่ 👶 Blockchain

    20/09/2019

    • อัพเดทบทความ 👶 Data Scientist เรื่อง หัดเขียน AI จาก AI ของคนอื่น (5/5)

    • เขียนบทความ C# version 8.0

    18/09/2019

    • อัพเดทบทความ 👶 Data Scientist เรื่อง แฉความลับของ AI Model (4/5)

    17/09/2019

    • อัพเดทบทความ 👶 Data Scientist เรื่อง หลักการตั้งคำถามให้ AI (3/5)

    16/09/2019

    • อัพเดทบทความ 👶 Data Scientist เรื่อง การเตรียมข้อมูลให้ AI (2/5)

    15/09/2019

    • อัพเดทบทความ 👶 Data Scientist เรื่อง การเลือก Algorithms ให้ AI (1/5)

    • เขียนบทความ 👶 Data Scientist

    14/09/2019

    • อัพเดท 👶 Machine Learning Studio เรื่อง สร้าง AI ตัดสินใจอนุมัติบัตรเครดิตกัน

    13/09/2019

    • อัพเดท 👶 Azure Cognitive Services เรื่อง เขียนแอพให้ AI อธิบายรูปเป็นภาษาคน

    12/09/2019

    • อัพเดท 👶 Machine Learning Studio เรื่อง มาสร้าง AI ของแท้ตัวแรกของเรากัน

    11/09/2019

    • เขียนบทความเรื่อว 👶 AI พื้นฐาน

    • ตั้งคอร์สใหม่ 👶 Machine Learning Studio

    10/09/2019

    • อัพเดท 👶 Azure Cognitive Services เรื่อง เขียนแอพ ทายอายุ บอกเพศ ง่ายจิ๊ดเดียว

    • อัพเดท 👶 Azure Cognitive Services เรื่อง การสร้าง Cognitive Services

    09/09/2019

    • อัพเดท 👶 Azure Cognitive Services เรื่อง เขียน AI แยกของต่างๆทำยังไง?

    08/09/2019

    • อัพเดท 👶 Azure Cognitive Services เรื่อง อ่านลายมือจากรูปเป็นตัวอักษร (OCR)

    • อัพเดท 👶 Azure Cognitive Services เรื่อง การ Login ด้วยใบหน้า

    07/09/2019

    • เขียนบทความเรื่อง 👶 Azure Cognitive Services

    • อัพเดท 👶 Azure Bot Service เรื่อง Bot เข้าใจเราได้ยังไงกันนะ

    • เขียนบทความเรื่อง 👶 Azure Bot Service

    06/09/2019

    • อัพเดท 👶 Azure Storage (Blobs) สร้างเว็บจากที่ฝากไฟล์บนคลาว์

    • อัพเดท 👶 Azure Storage (Blobs) ลองเขียนโค้ดอัพโหลดไฟล์กันบ้าง

    04/09/2019

    • อัพเดท 👶 Azure Storage (Blobs) เข้าใจ Blob storage ให้มากขึ้น

    03/09/2019

    • อัพเดท 👶 Azure Storage (Blobs) ลองสร้างที่เก็บไฟล์กันเลย

    • เขียนบทความเรื่อง 👶 Azure Storage

    02/09/2019

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 11 Auto Scaling

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 10 Guideline for cloud scaling

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 9 Cloud Native

    01/09/2019

    • อัพเดท 👶 Microsoft Azure 101 บทที่ 8 มาสร้าง Function App กัน

    • เขียนบทความเรื่อง 👦 Bottlenecks of Software

    Facebook Blog: Mr.Saladpuk
    ให้ใช้ Inheritance
    เมื่อมันเป็นของประเภทเดียวกันเท่านั้น
    👦 SOLID Design Principles

    15.625

    7

    7.8125

    8

    3.90625

    9

    1.953125

    10

    0.9765625

    ทั้งหมดก็ทำวนแบบนี้ไปเรื่อยๆ

    คนที่

    ขวดยาที่เหลือ (เริ่มที่ 1,000)

    1

    500

    2

    250

    3

    125

    4

    62.5

    5

    31.25

    เหตุการณ์ที่เกิดขึ้น

    ยาพิษอยู่ในกลุ่ม

    รอดทั้งคู่

    F

    คนที่ 2 ตายคนเดียว

    E

    คนที่ 1 ตายคนเดียว

    D

    ตายทั้งคู่

    C

    🧠 Challenges
    Saladpuk Fanclub

    6

    💖 Polymorphism
    🏭 Factory Method Pattern
    🏭 Abstract Factory Pattern
    ☝️ Singleton Pattern
    🏗️ Builder Pattern
    🎎 Prototype Pattern
    Mr.Saladpuk
    🤴 Design Patterns

    Polymorphism

    🤔 มันคืออะไร ?

    คำว่า Polymorphism มีใช้อยู่ในหลายวงการเลย แต่ในวงการซอฟต์แวร์ใน Wikipedia ถูกเขียนไว้ว่า

    Polymorphism is the provision of a single interface to entities of different types or the use of a single symbol to represent multiple different types.

    😑 ผมเชื่อว่าถ้าไม่เคยรู้จักมันมาก่อนจริงๆอ่านไปก็ไม่เข้าใจหรอก แต่ก็ถอดหัวใจสำคัญของมันออกมาได้ว่า

    Polymorphism คือทำให้ object เปลี่ยนรูปได้

    🤨 ก็ยัง งง อยู่ดีขอตัวอย่างหน่อย

    😢 ปัญหา

    สมมุติว่าเราต้องเขียน โปรแกรมบัญชีธนาคาร เหมือนเดิมละกัน โดยมันมี บัญชีออมทรัพย์ กับ บัญชีกระแสรายวัน โดยมีโค้ดแบบง่ายๆไว้ประมาณนี้

    แล้วสมมุติว่าเราต้องเขียนให้มัน ถอนเงิน ให้กับบัญชีทั้ง 2 แบบนี้ได้ล่ะ เราจะเขียนยังไง?

    ถ้าเอาแบบง่ายก่อน ก็สร้าง method ให้มันถอนเงินได้ใช่ป่ะ ตามโค้ดด้านล่าง

    มันก็ทำงานได้นะ แต่ลองคิดดูว่าถ้าเรามีบัญชีอีกหลายๆประเภทเข้ามาล่ะ? เราจะมี method พวกนี้กี่ตัว?

    🤔 วิเคราะห์ปัญหากันนิสนุง

    ปัญหาที่เราเจออยู่ตอนนี้คือ object ที่เราสร้างขึ้นมา มันจะต้องใช้กับ data type ที่มันเป็นอยู่เท่านั้น มันเลยทำให้เราไม่สามารถส่ง object ของ SavingAccount ไปยัง CurrentAccount ได้ และทางตรงข้ามกันเราก็ส่ง object ของ CurrentAccount ไปยัง SaveingAccount ไม่ได้ด้วยเช่นกัน เพราะ object มันผูกกับ data type ที่มันเป็นอยู่นั่นเอง

    😄 วิธีแก้ปัญหา

    ตามหัวข้อคือเราจะใช้ Polymorphism มาแก้ปัญหาในรอบนี้ยังไงล่ะ ซึ่งการใช้ใช้ Polymorphism ได้เราจะต้องใช้หลักของ Inheritance เข้ามาช่วยด้วยดังนั้นก็แก้โค้ดให้มันเป็นแม่ลูกกันหน่อยนึง ตามตัวอย่างในเรื่อง เลย (ถ้าจำไม่ได้กดที่ชื่อเพื่อกลับไปทบทวนซะ) ซึ่งเราก็จะได้ออกมาราวๆนี้

    ซึ่งหลักจากที่เราทำ Inheritance มาเรียบร้อย เราจะได้ความสามารถของ Polymorphism มาด้วย นั่นคือ

    การเปลี่ยนรูปได้ (ผมชอบใช้คำว่าโค้ดมันดิ้นได้) นั่นหมายความว่า object นั้นๆมันจะไม่ได้ผูกติดกับ data type ที่มันอยู่แล้วนั่นเอง

    ดังนั้นเราจะสามารถเขียนโค้ดแบบนี้ได้

    จะเห็นว่าตัว acc1 และ acc2 ทั้งคู่มันเป็นคลาส์ BankAccount แต่มันก็สามารถเก็บ object ที่ไม่ใช่ BankAccount ได้นั่นเอง ดังนั้นเราก็สามารถที่จะทำให้โค้ดของเรารองรับการทำงานของ บัญชีหลายๆรูปแบบในอนาคตได้แล้ว โดยการเขียนโค้ดเพียงแค่ตัวนี้เท่านั้น

    ไหนทดลองทำงานดูดิ๊

    Output (Saving) Saladpuk, has THB 0. (Current) Saladpuk, has THB -700.

    จะเห็นว่ามันทำงานได้ตามปรกติแถมยังทำงานถูกต้องตามแต่ละประเภทบัญชีด้วย บัญชีออมทรัพย์ถอนเงินเกินในบัญชีไม่ได้ แต่บัญชีกระแสรายวันถอนเงินเกินได้

    😵 Polymorphism คือไรกันแน่ ?

    ก่อนอื่นผมอยากให้เข้าใจตรงกันก่อนว่า โดยปรกติตัวแปรมันจะทำงานตามที่ type ที่มันเป็นอยู่เท่านั้น เช่น เรามีคลาส Dog กับ Cat

    ถ้าเราจะไปสร้าง object ของคลาส Dog ออกมา เราก็ต้องใช้ตัวแปรที่เป็นคลาส Dog มาเก็บมันเท่านั้น

    ไม่สามารถสร้าง object ของคลาส Dog ไปเก็บไว้กับตัวแปรที่เป็นคลาส Cat ได้เลย

    นี่แหละคือสิ่งที่เรียกว่า object มันผูกกับ data type ที่มันเป็นอยู่ นั่นเอง

    การที่โปรแกรมของเราสามารถทำ Polymorphism ได้มันจะเป็นการ ปลดล๊อค การผูกกันที่ว่านี้ เลยทำให้โค้ดเรายืดหยุ่นขึ้น นำกลับมาใช้งานใหม่ได้ง่ายขึ้น โดยตัวหนึ่งที่เรานำมาทำให้เกิด Polymorphism ได้นั่นก็คือการทำ Inheritance นั่นเอง ดังนั้นเมื่อเราแก้โค้ดให้เป็นแบบนี้

    มันเลยทำให้คลาส Dog และ Cat สามารถ เปลี่ยนรูป ตัวมันเองให้ตัวแปรที่เป็น Animal สามารถเก็บ object เหล่านั้นได้

    การเปลี่ยนรูป

    จากตัวอย่างด้านบนคือการเปลี่ยนรูป จาก Dog หรือ Cat กลายเป็น Animal เลยทำให้ตัวแปร Animal สามารถทำงานร่วมกัน Dog และ Cat ได้นั่นเอง โดยมันมีกฏอยู่ว่า

    Base Class สามารถเก็บ Sub Class ได้ แต่ทำตรงข้ามกันไม่ได้

    ถ้าเราแปลงโค้ดด้านบนเป็นรูปแล้วเราจะได้ออกมาเป็นแบบนี้

    นั่นหมายความว่า Animal สามารถเก็บ object ที่เป็น Sub Class ของมันได้นั่นคือ Dog และ Cat แต่เราไม่สามารถทำตรงข้ามกันได้ นั่นคือ Dog และ Cat ไม่สามารถเก็บ object ที่เป็น Animal ได้นั่นเอง

    และถ้าเรามองย้อนกลับไปถึงลำดับชั้นของการทำ Inheritance แล้วเราจะพบว่ามันเป็นรูปนี้

    เลยเป็นผลว่าทำไม Object เลยสามารถเก็บตัวแปรทุกอย่างในโค้ดเราได้นั่นเอง (มันเป็น base class สูงสุดนั่นเอง)

    Casting

    นอกจากที่เราจะเปลี่ยนรูปจาก Sub class ไปให้ Base class เก็บไว้ได้แล้ว เรายังสามารถเปลี่ยนรูปมันกลับมาก็ได้นะ เช่น

    Output Name: Saladpuk, BitPower: 50

    จากโค้ดด้านบนจะเห็นว่าแม้ Dog จะถูกเปลี่ยนรูปไปเป็น Animal ในบรรทัดที่ 14 แล้ว แต่เราก็สามารถแปลงมันกลับมาเป็น Dog ได้ตามปรกติ ในบรรทัดที่ 19

    Interface

    อีกตัวอย่างหนึ่งในการทำ Polymorphism นั่นคือการใช้ Interface นั่นเอง เช่น เครื่องใช้ไฟฟ้าสามารถเปิด/ปิดได้ โดยมีทีวีและมือถือเป็นเครื่องใช้ไฟฟ้า

    จากรูปเราก็สามารถทำ Polymorphism ได้เช่นกัน ตามนี้

    ลองใช้งานมันดู

    Output Tv - on Tv - off Mobile - on Mobile - off

    เกร็ดความรู้ Polymorphism มันมีหลามันมาจากคำ 2 คำรวมกันนั่นคือ (Poly = หลากหลาย) + (Morph = รูปร่าง) ดังนั้นพอรวมกันแล้วเลยเป็นกลายทำให้โค้ดของเรา เปลี่ยนรูป ได้นั่นเอง ซึ่งมันจะช่วยให้โค้ดของเรายืดหยุ่นขึ้นยังไงล่ะ

    🎥 วีดีโอประกอบความเข้าใจ

    ทฤษฎี

    ตัวอย่างการใช้งาน

    Action & Func

    😢 ปัญหา

    ทุกครั้งที่เราจะทำงานกับ delegate มันวุ่นวาย เพราะเราต้องสร้าง delegate ที่มี signature ที่อยากได้ก่อนเสมอถึงจะเริ่มใช้งานมันได้ มีวิธีไหนที่ทำงานกับมันได้ง่ายๆอีกไหม ?

    ถ้าอ่านแล้ว งงๆ ผมขอยกตัวอย่างว่า สมมุติผมอยากมีจะเก็บ method ในโค้ดด้านล่างนี้

    ไว้ในตัวแปรซักตัว ผมก็ต้องสร้าง delegate ขึ้นมาก่อน ตามนี้

    พอมีโค้ดด้านบนละ เราก็ถึงจะเริ่มไปสร้างตัวแปรมาเก็บ method AwesomeMethod ได้นั่นเอง ตามโค้ดด้านล่าง

    ซึ่งปัญหาคือถ้าจะทำแบบนี้เราก็ต้องไปสร้าง delegate ทุกครั้งเลยใช่ไหม ? มีง่ายกว่านี้ไหม?

    2019-10

    ✏️ ประวัติการอัพเดท

    สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

    public class Book
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
       public string ISBN { get; set; }
       public string Author { get; set; }
    }
    
    public class Cat
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
       public string OwnerName { get; set; }
    }
    
    public class Member
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
       public string LastName { get; set; }
       public string Address { get; set; }
    }
    public class ProductInformation
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
    }
    
    public class Book : ProductInformation
    {
       public string ISBN { get; set; }
       public string Author { get; set; }
    }
    
    public class Cat : ProductInformation
    {
       public string Ownername { get; set; }
    }
    
    public class Member : ProductInformation
    {
       public string LastName { get; set; }
       public string Address { get; set; }
    }
    public double GetTotalPrice(ProductInformation[] products)
    {
       ...
    }
    public class ProductInformation
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
    }
    
    public class Book : ProductInformation
    {
       public string ISBN { get; set; }
       public string Author { get; set; }
    }
    
    public class Cat : ProductInformation
    {
       public string OwnerName { get; set; }
    }
    public class Member
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
       public string LastName { get; set; }
       public string Address { get; set; }
    }
    public class Member
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
       public string LastName { get; set; }
       public string Address { get; set; }
    
       public bool IsVIP { get; set; }
    }
    public class Membership
    {
       public int Id { get; set; }
       public string Name { get; set; }
       public DateTime CreatedDate { get; set; }
       public string LastName { get; set; }
       public string Address { get; set; }
    }
    
    public class NormalMember : Membership
    {
    }
    
    public class VIPMember : Membership
    {
       public DateTime ExpiredDate { get; set; }
    }
    Dog dog = new Dog();

    แนะนำให้อ่าน หากยังไม่ได้อ่านเรื่อง delegate จงไปอ่านก่อนซะ ไม่นานหรอก Saladpuk - Delegates

    😄 วิธีแก้ปัญหา

    การแก้ปัญหาสั้นสุดแสนจะง่าย เพราะใน C# เขาได้เตรียมสิ่งที่เรียกว่า Action กับ Func มาให้เราเล่นแล้วยังไงล่ะ ดังนั้นไปดูกันทีละตัวเลยละกันนะ

    🔥 Action

    สมมุติว่าเราอยากเก็บ method ไว้ในตัวแปรซักตัว เราก็สามารถใช้ Action ไปเก็บเจ้า method นั้นได้เลย แต่เงื่อนไขคือเจ้า method ที่จะเก็บลงใน Action ได้นั้น มันจะต้องมี return type เป็น void เท่านั้นนะจ๊ะ

    เช่น ผมต้องการจะเก็บ method ด้านล่างนี้ไว้ในตัวแปรซักตัว

    เราก็สามารถสร้าง Action มาเก็บมันได้เลย เห็นมะเรียบง่ายดี ไม่ต้องสร้าง delegate ให้วุ่นวาย

    แบบรองรับ parameters

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

    เราก็สามารถสร้าง Action ที่มี signature ตรงกับ method ของเราได้เช่นกัน โดยการใช้ Generic เข้ามาช่วย ตามโค้ดด้านล่าง

    แนะนำให้อ่าน ใครยังไม่ได้อ่านเรื่อง Generic แนะนำให้ไปอ่านซะเพราะหลังๆเราจะใช้มันบ่อยมาก จนเรียกว่าไม่รู้จักมันแล้วละก็ชีวิตจะวุ่นวายขึ้นเยอะเลยเวลาเขียน C# Saladpuk - Generic

    🔥 Func

    หลังจากที่เราเห็น action ไปเรียบร้อยละ คราวนี้ก็จะเป็นคราวของ Func บ้าง ซึ่งสิ่งที่มันทำได้นั้นก็จะเหมือนกับ action เลย ต่างกันแค่ มันเอาไว้เก็บ method ที่มี return type นั่นเอง

    เช่นผมต้องการจะเก็บ method ตัวนี้ไว้ในตัวแปรซักตัว

    สิ่งที่เราต้องทำก็คือ ทำการสร้าง Func ที่มี signature ตรงกับ method ที่เราต้องการจะเก็บค่ามันไว้นั่นเอง ตามโค้ดด้านล่าง

    อธิบายโค้ดด้านบนนิดหน่อย เจ้า Func ก็เหมือนกับ Action แหละ ต่างกันแค่ตัวตัว Generic ตัวสุดท้ายของมันจะต้องเป็น return type ของ method นั่นเอง ซึ่งเจ้า method Add มี return type เป็น double ดังนั้นเลยทำให้ Func ของเราจะต้องมี Generic ตัวสุดท้ายเป็น double ด้วยนั่นเอง

    💡 Anonymous

    ในการเขียนโค้ดบางทีเราก็อยากได้ method ใหม่ แต่ไม่อยากเขียน method นั้นไว้ในคลาส เพราะมันจะทำให้คลาสของเรารกใช่ป่ะ ดังนั้นเราก็สามารถสร้าง method ในรูปแบบที่เรียกว่า anonymous ได้ครับ ซึ่งมันทำได้หลายแบบเลย ตามนี้

    Delegate

    เป็นการสร้าง anonymous method โดยการใช้ delegate เข้ามาช่วย

    คำแนะนำ วิธีการนี้เราจะไม่ค่อยได้เห็นแล้วนะ เพราะส่วนใหญ่เราจะใช้ Lambda แทนแล้วครับ แต่แค่อยากให้รู้ว่ามันก็เขียนแบบนี้ได้นะ เพราะในสมัยก่อนที่ C# 3.0 ยังไม่ออก เรายังไม่มี lambda มาใช้ยังไงล่ะ

    สมมุติว่าผมอยากได้ method ซักตัว ที่มันทำงานแบบโค้ดตัวนี้

    เราก็สามารถใช้ delegate ร่วมกับ Action ได้ครับ ก็จะได้ออกมาเป็นแบบนี้

    เพียงเท่านี้เราก็สามารถเพิ่ม method ใหม่ๆเข้าไปได้ละ โดยที่โค้ดเราก็จะรถเรื่อง method ละแต่ไปรถเรื่องตัวแปรแทน ฮ่าๆ

    Lambda

    เป็นการสร้าง anonymous method โดยการใช้ lambda เข้ามาช่วย ซึ่งเราจะพบโค้ดลักษณะนี้บ่อยมากถึงมากที่สุดในการเขียน C# ครับ

    สมมุติว่าผมอยากได้ method ซักตัว ที่มันทำงานแบบโค้ดตัวนี้

    เราก็สามารถใช้ lambda ร่วมกับ Action ได้ครับ ก็จะได้ออกมาเป็นแบบนี้

    แนะนำให้อ่าน Lambda คืออะไรสามารถอ่านได้จากบทความนี้ครัช Saladpuk - Lambda expression

    🤔 ทำไมต้องใช้ของพวกนี้ด้วย ?

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

    คำตอบก็คือ ใช่ครับเราไม่ต้องใช้ของพวกนี้เลยก็ได้เพราะเราเรียกใช้ method ได้เลยสะดวกดี แต่ในบางสถานะการณ์ ถ้าเราไม่ใช้ความสามารถของพวกนี้มันจะทำให้เราทำงานลำบากมาก ยกตัวอย่างเช่น เราอยากจะให้โปรแกรมของเราไปทำงาน A ให้เสร็จก่อน และเมื่อมันทำงานเสร็จแล้ว ค่อยกลับมาเรียกใช้งาน B ต่อนะ ซึ่งเราเรียกลักษณะการทำงานแบบนี้ว่า Callback ครับ ดังนั้นมาดูกันว่าการใช้ action หรือ func มาช่วยเรื่องการทำ callback นั้นมันจะช่วยแก้ปัญหานี้ได้ยังไงกัน

    สมมุติว่าผมมีคลาสตัวนึงที่มี method 2 อัน

    ซึ่งผมต้องการให้มันทำงาน method A เสร็จก่อน แล้วค่อยเรียกใช้ method B เราจะทำยังไง? สมมุติว่าการทำงานของ A มันเป็น asynchronous

    Asynchronous & Synchronous โดยปรกติโค้ดที่เราเขียนอยู่นั้นมันจะทำงานในลักษณะที่เรียกว่า Synchronous ซึ่งมันจะรอจนกว่าโค้ดบรรทัดนั้นจะทำงานจนเสร็จถึงจะไปทำงานบรรทัดอื่นต่อ แต่การทำงานของ Asynchronous จะตรงกันข้ามกัน คือมันจะไม่รอ เมื่อสั่งทำงานโค้ดที่เป็น asynchronous ไปละ มันก็จะไปทำงานบรรทัดต่อไปเลย

    ซึ่งในตัวอย่างคือถ้าเราเรียกใช้งาน method A ปุ๊ป มันก็จะไปทำงานอย่างอื่นต่อ ไม่รอให้ method A ทำงานจนเสร็จ ดังนั้นถ้าเราเรียก method B ต่อจากการเรียก method A มันจะมีโอกาสทำงาน A กับ B พร้อมกันได้ ซึ่งทำให้ลำดับการทำงานไม่ตรงตามที่เราต้องการนั่นเอง

    คำตอบก็คือ เราก็แค่ส่ง method B เข้าไปเป็น parameter ของ method A ยังไงล่ะ ซึ่งพอ method A ทำงานเสร็จ มันก็จะเรียกใช้ method B ต่อเลยนั่นเอง ดังนั้นโค้ดเราก็จะออกมาเป็นภาพนี้

    static void AwesomeMethod()
    {
        Console.WriteLine("Hello world");
    }
    delegate void SALADPUK_DELEGATE();
    static void Main(string[] args)
    {
        SALADPUK_DELEGATE d = AwesomeMethod;
        d();  // Hello world
    }
    static void AwesomeMethod()
    {
        Console.WriteLine("Hello world");
    }
    static void Main(string[] args)
    {
        Action a = AwesomeMethod;
        a();
    }
    static void CalculatorMethod(int a, int b, int c)
    {
        var result = a + b + c;
        Console.WriteLine($"The result is: {result}");
    }
    static void Main(string[] args)
    {
        Action<int, int, int> a = CalculatorMethod;
        a(1, 5, 7);
    }
    static double Add(int first, int second)
    {
        var result = first + second;
        return result;
    }
    static void Main(string[] args)
    {
        Func<int, int, double> f = Add;
        var result = f(5, 9);
        Console.WriteLine(result);
    }
    static void Add(int first, int second)
    {
        var result = first + second;
        Console.WriteLine(result);
    }
    static void Main(string[] args)
    {
        Action<int, int> a = delegate (int first, int second)
        {
            var result = first + second;
            Console.WriteLine(result);
        };
        a(5, 9);
    }
    static void Add(int first, int second)
    {
        var result = first + second;
        Console.WriteLine(result);
    }
    static void Main(string[] args)
    {
        Action<int, int> a = (first, second)=>
        {
            var result = first + second;
            Console.WriteLine(result);
        };
        a(5, 9);
    }
    static void A()
    {
        Console.WriteLine("A");
    }
    
    static void B()
    {
        Console.WriteLine("B");
    }
    static void A(Action callback)
    {
        Console.WriteLine("A");
        callback();
    }
    
    static void B()
    {
        Console.WriteLine("B");
    }
    31/10/2019
    • เพิ่มบทความ 👶 DevOps พื้นฐาน

    • ร่างคอร์ส 👶 Azure DevOps

    28/10/2019

    • เพิ่ม เกร็ดความรู้ เรื่อง การทำซอต์แวร์โปรเจคยุคปัจจุบัน

    27/10/2019

    • เพิ่ม 🤔 อ่านเรื่องไรดี? เพื่อให้ง่ายในการหาคอร์สที่ตัวเองสนใจ

    26/10/2019

    • เพิ่ม เกร็ดความรู้ เรื่อง เคล็ดไม่ลับในการทำซอฟต์แวร์

    24/10/2019

    • อัพเดทบทความ 👦 Agile Methodology เรื่อง Code Review

    23/10/2019

    • เพิ่ม เกร็ดความรู้ เรื่อง Quality vs Quantity

    22/10/2019

    • อัพเดทบทความ 👶 Azure Web App เรื่อง เซิฟเวอร์บนคลาว์ ราคา? ต่าง?

    • ตั้งคอร์ส 👶 Azure Web App

    21/10/2019

    • เพิ่ม เกร็ดความรู้ เรื่อง ปัญหาสมองไหล

    • เพิ่ม เกร็ดความรู้ เรื่อง เรื่องแปลกในการทำซอฟต์แวร์

    20/10/2019

    • เพิ่ม เกร็ดความรู้ เรื่อง ปัญหาที่ใหญ่ที่สุดในการทำซอฟต์แวร์

    • เพิ่ม เกร็ดความรู้ เรื่อง เมื่อเราไม่เข้าใจกัน

    • เพิ่ม เกร็ดความรู้ เรื่อง กฎ 80:20

    • สร้างโซน เพื่อเอาไว้รวบรวมเทคนิคหรือเรื่องสั้นที่อยากเล่าให้ฟัง

    19/10/2019

    • อัพเดทบทความ 👨 C# ระดับสูง เรื่อง พระคัมภีร์การใช้คำสั่ง LINQ

    • อัพเดทบทความ 👨 C# ระดับสูง เรื่อง LINQ

    17/10/2019

    • ตั้งคอร์ส 👶 Power BI

    16/10/2019

    • อัพเดทบทความ 👦 Security พื้นฐาน เรื่อง การเก็บรหัสผ่านที่ถูกต้อง

    15/10/2019

    • ตั้งคอร์ส 👦 Security พื้นฐาน

    14/10/2019

    • อัพเดทบทความ 👨 C# ระดับสูง เรื่อง Lambda expression

    • อัพเดทบทความ 👨 C# ระดับสูง เรื่อง Action & Func

    13/10/2019

    • อัพเดทบทความ 👨 C# ระดับสูง เรื่อง Generic

    • อัพเดทบทความ 👨 C# ระดับสูง เรื่อง Delegates

    • อัพเดทบทความ C# Tips เรื่อง 💡 Boxing & Unboxing

    12/10/2019

    • อัพเดทบทความ 👦 Bottlenecks of Software เรื่อง พื้นฐานที่สำคัญที่สุดของฐานข้อมูล

    11/10/2019

    • อัพเดทบทความ 👦 Agile Methodology เรื่อง Software Development Life Cycle

    10/10/2019

    • อัพเดทบทความ 👦 Agile Methodology เรื่อง Agile in a Nutshell

    09/10/2019

    • ตั้งคอร์ส 👦 Agile Methodology

    05/10/2019

    • อัพเดทบทความ 👶 Microservices พื้นฐาน เรื่อง จาก Monolith สู่ Microservices

    04/10/2019

    • อัพเดทบทความ 👶 Microservices พื้นฐาน เรื่อง Microservices Tips

    03/10/2019

    • อัพเดทบทความ 👶 Microservices พื้นฐาน เรื่อง Microservices มีลักษณะยังไง

    02/10/2019

    • เขียนบทความ 👶 Microservices พื้นฐาน

    • ตั้งคอร์ส 👶 Azure Service Fabric

    01/10/2019

    • อัพเดทบทความ 👶 UML พื้นฐาน เรื่อง บทสรุปการใช้ UML

    Facebook Blog: Mr.Saladpuk
    Inheritance

    Algorithm

    🤨 โครงสร้างข้อมูลเรียนไปทำไม? โตไปไม่ได้ใช้ ?

    😢 ปัญหา

    หลายคนที่เรียน Computer Science หรือ Computer Engineering มาก็คงจะหนีไม่พ้นที่จะถูกบังคับให้เรียน Data Structure & Algorithm มาชิมิ แล้วพอมาอยู่ในโลกของการเขียนซอฟต์แวร์จริงๆก็อาจจะมีคำถามว่า ไอ้วิชาบร้านี้เรียนมาทำไมฟระ เสียเวลาชีวิตจุง 🤬

    ดังนั้นบทความนี้แมวน้ำจะมาแฉให้ฟังว่า วิชานี้คือตัวที่เอาไว้แบ่งระหว่าง คนที่เขียนโค้ดได้ กับ คนที่ออกแบบ Software Architecture ได้ หรือพูดแบบบ้านๆก็คือ แค่โบกปูนเป็นหรือคนที่สร้างตึกระฟ้าได้นั่นเอง

    ฟหกดเอกอาสว!@#$%^&* 😎 โครงสร้างซอฟต์แวร์จะเทพหรือกากมันคือเรื่องนี้เบย

    ❓ Data Structure & Algorithm คือไย ?

    ขอเกริ่นให้คนที่ไม่ได้เรียนวิชานี้ หรือลืมไปแบ้วนิสนุง (ใครจำได้ก็ข้ามไปดูด้านล่างโลด)

    Algorithm

    เวลาที่เราเจอปัญหาอะไรก็ตาม เช่น อยากให้โปรแกรมทำนั่นนู่นนี่ได้ สิ่งที่เราจะต้องทำก็คือ หาวิธีการแก้ไขปัญหา หรือนั่นก็คือ Algorithm นั่นเอง ซึ่งขั้นตอนนี้เราจะคิดออกมาเป็นขั้นตอน 1 2 3 4 โดยไม่ยึดติดว่ามันเป็นภาษาอะไรนั่นเอง ซึ่งปัญหาที่เราเจออาจจะมีวิธีการแก้ปัญหาหลายๆแบบก็ได้ (มี Algorithm มากกว่า 1 ตัว)

    สรุปสั้นๆ Algorithms คือการ Define steps ที่ใช้ในการแก้ปัญหา

    Data Structure

    หลังจากที่เรารู้แล้วว่าเราจะแก้ปัญหานั้นยังไง เราก็จะเลือกวิธีการแก้ปัญหาที่เราคิดว่าดีที่สุดมา แล้วเราก็จะเอามันไปออกแบบว่า ถ้าจะเขียนโค้ดเพื่อให้มันทำงานตาม Algorithm นั้นๆได้ เราจะออกแบบ class ต่างๆ module ต่างๆยังไง เพื่อให้มันทำงานตาม algorithm นันๆได้นั่นเอง และแน่นอนว่า 1 Algorithm ก็สามารถมี Data Structure ได้มากกว่า 1 แบบเช่นกัน

    สรุปสั้นๆ Data Structure คือการ Define structure ที่ใช้ในการเขียนโค้ดให้ได้ตาม Algorithm นั้นๆ

    จากที่มโนมาทั้งหมดเราก็จะเห็นว่า Algorithm กับ Data Structure เป็นของที่ เกิดมาคู่กัน โดยมันมีเป้าหมายหลักอันเดียวกันคือ แก้ไขปัญหาที่เรากำลังเจออยู่นั่นเอง ซึ่งแต่ละ Algorithm แต่ละ Data Structure ก็จะมีข้อดี ข้อเสียที่แตกต่างกันไป

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

    😅 มันจำเป็นต้องรู้ด้วยเหรอ?

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

    🤔 พูดให้เห็นภาพหน่อยได้ไหม?

    จัดไป! ... สมมุติว่าเราต้องเขียนเว็บง่ายๆ 1 ตัว แล้วให้มันมี ระบบนับยอดคนดู เราจะทำยังไง?

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

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

    อะเช พอมาถึงจุดนี้ความสำคัญของ Algorithm & Data Structure มันจะเริ่มแผลงฤทธิ์ให้เราเห็นความสำคัญของมันมากขึ้นคือ โครงสร้างที่เป็น Centralized system แบบในรูปด้านบน มันจะมีคอขวดที่จะทำให้ระบบล่มได้ง่ายๆ เช่น สมมุติว่าตัว Database ที่เลือกใช้มีความสามารถรับการเขียนได้ 100 ครั้ง/วินาที แล้วจะเกิดอะไรขึ้นถ้ามีคนเข้าเว็บเกิน 100 คน/วินาทีกันล่ะ? . . . เมื่อหัวใจมันล่มระบบก็ตายนั่นเอง

    Single point of failure จุดอ่อนของระบบที่เป็น Centralized System คือ ถ้าตัวกลางตาย ทั้งระบบก็จะมีปัญหา ซึ่งการแก้ปัญหาแบบกากสุดก็คือ เพิ่มตัวกลางหลายๆตัว ซึ่งมันก็จะมีปัญหาในอีกรูปแบบตามมา เช่นการทำ synchronization บลาๆ

    จากที่ว่ามานี่คือตัว ขีดจำกัดของ Architecture ประเภทนี้ ดังนั้นถ้าเราอยากที่จะแก้ปัญหาที่เป็น Centralized System เราก็จะต้องเลือก Algorithm & Data Structure ที่เหมาะกับ Architecture ของมันนั่นเอง

    😒 Algorithm มันเกี่ยวไร ?

    จากปัญหาตัวนับคนที่ว่ามา ถ้าเราเลือก Algorithm ที่ต่างกัน ผลลัพท์กับ Architecture ก็จะไปกันคนละโลกเลย เช่น เรายอมให้ตัวเลขคนเข้าเว็บมันคลาดเคลื่อนได้นะ แต่ถ้าปล่อยไว้ซักพักเดี๋ยวมันจะถูกเอง ซึ่งเราเรียกของแบบนี้ว่า นั่นเอง

    Eventual consistency เป็นหลักที่ยอมให้ไม่สอดคล้องกันได้ แต่สุดท้ายจะสอดคล้องกันเอง พูดแล้วอาจจะ งงๆ แต่ที่เราเคยเจอแน่ๆคือ ยอดคนดูวีดีโอของ Youtube ยังไงล่ะ มันไม่ใช่ยอดที่อัพตลอดเวลา ซึ่งวีดีโอเดียวกันอาจจะเห็นยอดคนดูต่างกันก็ได้ ซึ่งปล่อยทิ้งไว้ซักพัก เดี๋ยวเลขนั้นมันก็จะเป็นยอดคนดูที่ถูกต้องเอง ซึ่งแนวคิดนี้มาจากการทำ Distrubuted System ยังงุยล่ะ

    จากที่ว่ามาเมื่อเราเลือก Algorithm ที่เป็น Eventual consistency แล้ว เราก็ต้องมี Data Structure ที่เหมาะกับกับ Architecture ในลักษณะนั้นด้วยมันถึงจะทำงานร่วมกันได้ ตามรูปเลย

    อธิบายรูปนิสนุง เซิฟเวอร์แต่ละตัวทำการนับยอดของใครของมัน ซึ่งเมื่อยอดตัวมันเองเปลี่ยน มันก็จะแจ้งไปบอกเพื่อนๆของมันด้วยเสมอ ทำให้แต่ละเซิฟเวอร์ทำงานแยกออกจากกันได้ ไม่เกิด Single point of failure ขึ้น

    เหมือนเรื่องราวจะจบแล้ว แต่ยัง!! เพราะอย่างที่เคยบอกไปว่าทุก Algorithm ทุก Architecture ย่อมมีข้อดี และ ข้อเสีย หรือ ขีดจำกัดทางสายเลือดของมันเสมอ ซึ่งจากรูปด้านบน การทำงานของเซิฟเวอร์แต่ละตัวจะต้องส่งผ่าน network / internet ดังนั้นมันก็จะมีปัญหาเรื่อง ส่งถึงบ้างไม่ถึงบ้าง, ข้อมูลที่ส่งไปก่อนถาจจะถึงช้ากว่าตัวที่ส่งไปทีหลัง, Concurrency บลาๆ ซึ่งทั้งหมดทั้งมวลมันจะก่อให้เกิดสิ่งที่เรียกว่า ความขัดแย้ง (Conflict) ได้ เช่น เซิฟเวอร์ A บอกว่ายอดรวมตอนนี้เป็น 3 แสนคนนะ แต่ B บอกว่าไม่ใช่ 5 แสนต่างหาก ส่วน C บอกว่า 9 ล้านคนเฟร้ย และ D บอกว่าของตรูพึ่งนับได้ 1 พันคนเอง ... แล้วเราจะแก้ปัญหาความขัดแย้งนี้ยังไง?

    Conflict อย่างที่บอกว่าทุกอย่างย่อมมีปัญหาของมัน ซึ่งหนึ่งในการแก้ปัญหานี้ของ Distructured system คือการทำ (สนใจก็กดที่ชื่อมันไปอ่านต่อว่าทำยังไงเอานะ ผมอธิบายไว้ในบทความของ Blockchain พื้นฐานไว้ละ)

    และจากที่เคยบอกว่า ปัญหา ไม่ได้มีคำตอบเดียวเสมอ และคำตอบแต่ละอันก็จะมีจุดเด่น/ข้อจำกัดที่ต่างกัน ดังนั้นถ้าเรารู้จัก Algorithms หลายๆแบบ เราก็จะสามารถ เลือกสิ่งที่เหมาะสมกับระบบได้อย่างมีประสิทธิภาพ เช่น ในตัวอย่างนี้ถ้าไม่อยากวุ่นวายเราก็สามารถทำ Strong eventual consistency ได้ แต่มันจะเหมาะสมกับหน้างานหรือเปล่า และข้อจำกัดของมันคืออะไร ก็ต้องว่ากันไปตามโจทย์ที่เราเจอ

    🎯 บทสรุป

    เวลาที่เจอโจทย์ สิ่งที่โปรแกรมเมอร์ควรจะต้องทำไม่ใช่เขียนโค้ด แต่เป็นการมองหา Solution ที่เหมาะกับปัญหา และเลือก Data structure ที่เหมาะสมกับปัญหา + Architecture ที่เจอ แล้วเราจะรู้ขีดจำกัดทางสายเลือดของมัน เพราะโปรแกรมเมอร์ที่เป็นตำแหน่งสูงขึ้นที่แท้จริงคือ ความแม่นยำในการออกแบบและตัดสินใจนั่นเอง

    ยิ่งเรามีความรู้ของ Architecture & Algorithm ที่เยอะขึ้น เวลาที่มี feature ใหม่ๆเข้ามา เราก็จะรู้ได้ทันทีว่า Architecture ที่เรามีสามารถรองรับการทำงานพวกนั้นได้หรือเปล่า มิเช่นนั้นมันจะเหมือนกับเราจะขับรถไปญี่ปุ่น โดยที่ลืมคิดเลยว่า ญี่ปุ่นมันเป็นเกาะมีแต่ทะเลล้อมรอบ แล้วเราก็จะมีงานงอกเพื่อให้เราขับรถไปถึงญี่ปุ่นได้โดยไม่จำเป็น เพราะถ้าเรามองแต่แรกเราก็จะพบ Solution อื่นที่อาจจะดีกว่าก็ได้ เช่น นั่งเครื่องบิน

    ท่องไว้จำจงดี 1 ซอฟต์แวร์ ➡️ มีหลายปัญหา (Problems) 1 ปัญหา ➡️ มีหลายคำตอบ (Algorithms) 1 คำตอบ ➡️ เขียนโค้ดได้หลายแบบ (Data Structure) 1 ปัญหา ➡️ อาจเกิดจากหลาย Context หลาย Domain ดังนั้นตอนที่แก้ปัญหาต้องดูให้ครบทุก Context & Domain มิเช่นนั้น มันจะแก้ตรงนี้ได้ แต่มีปัญหางอกที่จุดอื่นที่เกี่ยวข้องกับมัน

    1 คำตอบ ➡️ อาจเป็นวิธีที่ดีที่สุดสำหรับบางสถานะการณ์ และอาจเป็นวิธีที่ห่วยที่สุดในบางสถานะการณ์ก็ได้

    1 Data Structure ➡️ อาจะเป็นวิธีที่เร็วที่สุด แต่อาจจะไม่เหมาะสมกับ Architecture ที่ใช้อยู่ก็ได้

    Prototype Pattern

    แนวคิดในการก๊อปปี้ object แบบง่ายๆ

    เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า ต้นแบบ ซึ่งมันอยู่ในกลุ่มของ 🤰 โดยเจ้าตัวนี้มันจะมาช่วยให้เราสามารถ ก๊อปปี้ object ตัวหนึ่ง ออกไปเป็นอีกตัวหนึ่งได้แบบง่ายๆ ซึ่งอ่านแล้วก็อาจจะ งงๆ ว่าทำไมต้องทำ ดังนั้นไปดูตัวอย่างปัญหาของเรากันดีกั่ว

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช

    public class SavingAccount
    {
       public void Withdraw(double amount) { ... }
    }
    
    public class CurrentAccount
    {
       public void Withdraw(double amount) { ... }
    }
    static void Main(string[] args)
    {
        var sa = new SavingAccount();
        Withdraw(sa, 500);
    
        var ca = new CurrentAccount();
        Withdraw(ca, 700);
    }
    
    static void Withdraw(SavingAccount account, double amount)
    {
        account.Withdraw(amount);
    }
    
    static void Withdraw(CurrentAccount account, double amount)
    {
        account.Withdraw(amount);
    }
    public class BankAccount { ... }
    public class SavingAccount : BankAccount { }
    public class CurrentAccount : BankAccount { ... }
    BankAccount acc1 = new SavingAccount();
    BankAccount acc2 = new CurrentAccount();
    static void Withdraw(BankAccount account, double amount)
    {
        account.Withdraw(amount);
    }
    static void Main(string[] args)
    {
        BankAccount acc1 = new SavingAccount { OwnerName = "(Saving) Saladpuk" };
        Withdraw(acc1, 500);
        Console.WriteLine($"{acc1.OwnerName}, has THB {acc1.Balance}.");
    
        BankAccount acc2 = new CurrentAccount { OwnerName = "(Current) Saladpuk" };
        Withdraw(acc2, 700);
        Console.WriteLine($"{acc2.OwnerName}, has THB {acc2.Balance}.");
    }
    public class Dog { }
    public class Cat { }
    Dog d1 = new Dog();
    Cat c1 = new Dog(); // Error
    public class Animal { }
    public class Dog : Animal { }
    public class Cat : Animal { }
    Animal dog = new Dog();
    Animal cat = new Cat();
    Dog dog = new Animal(); // Error
    Cat cat = new Animal(); // Error
    object animal = new Animal();
    object dog = new Dog();
    object cat = new Cat();
    public class Animal
    {
        public string Name { get; set; }
    }
    
    public class Dog : Animal
    {
        // พลักในการกัด
        public int BitePower { get; set; }
    }
    
    static void Main(string[] args)
    {
        Animal animal = new Dog
        {
            Name = "Saladpuk",
            BitePower = 50,
        };
        Dog dog = (Dog)animal;
        Console.WriteLine($"Name: {dog.Name}, BitePower: {dog.BitePower}");
    }
    public interface IElectronic
    {
        void TunrOn();
        void TunrOff();
    }
    
    public class Television : IElectronic
    {
        public void TunrOff() => Console.WriteLine("Tv - off");
        public void TunrOn() => Console.WriteLine("Tv - on");
    }
    
    public class Mobile : IElectronic
    {
        public void TunrOff() => Console.WriteLine("Mobile - off");
        public void TunrOn() => Console.WriteLine("Mobile - on");
    }
    IElectronic e1 = new Television();
    e1.TunrOn();
    e1.TunrOff();
    
    IElectronic e2 = new Mobile();
    var mobile = (Mobile)e2;
    mobile.TunrOn();
    mobile.TunrOff();
    เกร็ดความรู้
    หมายเหตุ เนื้อหาของบทความนี้จะเน้นให้เข้าใจหลักการทำงานของ Design Patterns แต่ละตัว โดยใช้เกม Ragnarok เป็นการอธิบาย ซึ่งบางอย่างอาจจะไม่ตรงกับตัวเกมจริงๆนะขอรับ Gravity อย่ามาจับผมนะผมโดนแมวน้ำครอบงำ + รู้เท่าไม่ถึงการ + ผมเป็นคนดี + ผมมีลูกมีเมียมีสามีที่ต้องดูแล 😭

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย Mr.Saladpuk และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    🧐 โจทย์

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

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

    อยากได้ object ใหม่ที่มีข้อมูลเหมือนตัวเดิมเลยอ่าาาา

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

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

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

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

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

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

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

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

    หมวกของ object 1 ถูกเปลี่ยนด้วยเฉยเลย

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

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

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

    อุ๊ต๊ะมี private member ด้วย !

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

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

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

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

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

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

    ใช้คลาสอื่นๆเต็มไปหมดเบย

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

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

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

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

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

    แนะนำให้อ่าน สำหรับใครที่อยากศึกษาต่อว่า Value type กับ Reference type คืออะไร สามารถไปทำความเข้าใจได้กับบทความตัวนี้ขอรับ Value type vs Reference type มันเป็นเรื่องพื้นฐานสุดๆของการเขียนโปรแกรม ดังนั้นขอไม่ลงรายละเอียดตัวนี้ต่อนะ

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

    แนะนำให้อ่าน อะไรนะลืมการทำ encapsulation ไปแล้วรึ งั้นไปทบทวนได้จากลิงค์นี้เบย 💖 Encapsulation

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

    แนะนำให้อ่าน หลักในการออกแบบขั้นพื้นฐานคือการ ทำงานกับ Abstraction นั่นเอง ซึ่งถ้าอยากศึกษาว่าทำไมต้องเป็นแบบนั้นก็ไปอ่านได้จากลิงค์นี้ Creational Pattern ส่วนถ้าอยากเข้าใจลึกๆถึงแก่นจริงๆลองศึกษาได้จากบทความนี้ Dependency-Inversion Principle

    🔥 แก้ไขปัญหา

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

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

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

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

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

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

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

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

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

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

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

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

    ทำเป็น abstract class ซะ

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

    ทำเป็น interface กลางๆ ใช้ได้ทุกสถานะการณ์

    🤔 Prototype คือไย ?

    🔥 จุดกำเหนิด

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

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

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

    🔥 วิธีการใช้

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

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

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

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

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

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

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

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

    🎯 บทสรุป

    👍 ข้อดี

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

    👎 ข้อเสีย

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

    เกร็ดความรู้ Circular references คือการที่ object A ไปอ้างถึง object B และเจ้า object B ก็ดันมีการอ้างกลับมาที่ object A เช่นกัน ตามโค้ดด้านล่างนี้

    🤙 ทางเลือก

    โดยปรกติเรื่องการก๊อปปี้ object นั้นเป็นปัญหามานานแล้ว ดังนั้นในแต่ละภาษาเขาจะมีวิธีการจัดการของเขาเองอยู่แล้ว เช่นของภาษา C# ก็จะมี interface ที่ชื่อว่า ICloneable อยู่แล้วเราไม่ต้องไปสร้างเอง หรือพวกเมธอด MemberwiseClone ดังนั้นจงไปศึกษามาตรฐานของภาษาที่ตัวเองใช้ให้ดีก่อนนะขอรับ

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

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย Mr.Saladpuk และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    Creational Patterns
    👦 Design Patterns
    ห.ร.ม. (หารร่วมมาก)
    Eventual consistency
    Consensus algorithm
    เข้าใจหลักพื้นฐานนี้คได้ก็เริ่มอ่าน Blockchain ต่อได้เลยนะ หุหุ

    Proxy Pattern

    แนวคิดในการควบคุม object ให้ทำงานดั่งใจ 😈

    เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า ผู้ควบคุม ละกัน ซึ่งมันอยู่ในกลุ่มของ 🧱 Structural Patterns โดยเจ้าตัวนี้จะมาช่วยแก้ปัญหาเมื่อ เราต้องการจะควบคุมพฤติกรรม object ให้ได้ดั่งใจ พูดแล้วก็ งงๆ ไปดูโจทย์ของเรากันเลยดีกว่าจะได้เข้าใจได้เร็วขึ้น

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช 👦 Design Patterns

    หมายเหตุ เนื้อหาของบทความนี้จะเน้นให้เข้าใจหลักการทำงานของ Design Patterns แต่ละตัว โดยภาพจากการ์ตูน One Piece มาใช้ประกอบ ซึ่งหลายๆอย่างนั้นมโนขึ้นมาเพื่อความสนุก และทำให้เข้าใจเนื้อหาได้ง่าย ลิขสิทธิ์ต่างๆอย่ามาจับผมนะผมโดนแมวน้ำครอบงำ + รู้เท่าไม่ถึงการ + ผมเป็นคนดี + ผมมีลูกมีเมียมีสามีที่ต้องดูแล 😭

    🧐 โจทย์

    สมมุติว่ามี Web API ตัวหนึ่งชื่อ พรหัก ที่สอนธรรมะแบบแปลกๆแก่ชาวโลกอยู่ตัวหนึ่ง ซึ่งรัฐบาลโลกเห็นว่าเป็นภัยต่อความมั่นคง พลโท.การ์ป จึงสั่งการแบนมันทิ้งอย่างฉับพลัน

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

    1. ผู้ใช้ทุกคนต้อง ใช้งาน API ได้เหมือนเดิมทุกอย่าง

    2. รัฐบาลโลกสามารถ ควบคุม เนื้อหาที่คิดว่าเป็นภัยต่อความมั่นคงได้

    3. รัฐบาลโลก กำหนดสิทธิในการเข้าถึง เนื้อหาต่างๆได้

    เวลาที่ผู้ใช้จะเข้าเว็บอะไรก็ตาม มันจะต้องผ่าน ผู้คุมกฎ ก่อนเสมอ เพื่อให้ผู้คุมกฎไปเอาผลลัพท์กลับมาให้ดูนั่นเอง

    🤔 ดังนั้นเราในฐาน Developer ที่ทำงานเป็นผู้คุมกฎ จะออกแบบยังไงถึงจะทำได้ครบทุกข้อตามที่รัฐบาลโลกสั่งมา ?

    หากไม่เห็นผมเขียนบทความใหม่ๆแล้ว ก็ฝากไปประกันตัวผมด้วย หรือว่างๆก็แวะมาทักทายกันได้นะ (ผมชอบกินเนื้อย่าง) 😭

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

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

    เข้าใช้งานตามปรกติได้ ใครขออะไรมาก็ทำให้เขาไป แต่ขอตรวจเนื้อหาก่อนที่จะส่งไปให้ก่อนนะ

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

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

    ก็ดูเหมือนจะได้เกือบครบหมดแล้วนะ ดังนั้นเขียนโค้ดนิดหน่อยละกัน ครืดๆ

    โดยจากรูปด้านบนจะเห็นว่า เมื่อไหร่ก็ตามที่ clients ขอดูเว็บ เราในฐานะผู้คุมกฎก็จะไปเรียกใช้ ApiRequestHandler ไปดึงข้อมูลต่อให้ ดังนั้นโค้ดที่ใช้ในการตรวจสอบเงื่อนไขสิ่งต่างๆก็จะอยู่ภายในคลาส ApiRequestHandler นั่นเอง

    ดังนั้นโค้ดก็น่าจะออกมาราวๆนี้

    อะเชจากโค้ดด้านบนก็สามารถตอบโจทย์ทุกอย่างได้หมดละ แต่มันจะเกิดอะไรขึ้น ถ้าเราอยากควบคุม api มากว่านี้ล่ะ? ... หรือ อยากเพิ่มเงื่อนไขเฉพาะเว็บนี้แต่เว็บอื่นไม่ต้องสนใจละ? ... หรือ ถ้า API ของพรหักเปลี่ยนล่ะ? ... โค้ดใน ApiRequestHandler ก็จะต้องถูกแก้ไขอะดิ 😨

    เพราะโค้ดของเรามันมีหลายอย่างที่ไม่ตรงหลักในการออกแบบที่ดี เช่น SRP, OCP, DIP ยังไงล่ะ

    Single-Responsibility Principle (SRP) การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ เวลาที่ Requirement เปลี่ยนมาทีนึง มันก็จมีโอกาสสูงมากที่การเปลี่ยนนั้นมันจะไปกระทบเจ้าสิ่งนั้น ทำให้เราต้องแก้ไขมัน ซึ่งผองเพื่อนอื่นๆที่มันดูแลอยู่นั้นไม่ได้เกี่ยวข้องเลยก็มีผลกระทบด้วยนั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย

    Open & Close Principle (OCP) การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้

    Dependency-Inversion Principle (DIP) การละเมิดกฎข้อนี้จะทำให้ module หลักต้องถูกแก้ไขบ่อยๆ เมื่อตัวที่ทำงานตัวเล็กตัวน้อยมีการเปลี่ยนแปลง แม้จะเปลี่ยนเพียงแค่เล็กน้อยก็ตาม

    😢 เอาน่าไม่ต้องเสียใจไป เดี๋ยวเราลองวิเคราะห์ปัญหาแล้วลองแก้ไขมันไปทีละปมดูละกันนะ

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

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

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

    🔥 แก้ไขปัญหา

    เมื่อเรามีของที่เราควบคุมไม่ได้ เราต้องเปลี่ยนเป็นสิ่งที่เราควบคุมได้ก่อน เพราะไม่อย่างนั้นสนุกแน่ ซึ่งการที่เราจะไปควบคุมของอื่นๆมีหลายวิธีเลย ซึ่งวิธีง่ายสุดคือการทำ Wrapper Class โดยเราจะสร้างคลาสขึ้นมาครอบของที่ควบคุมไม่ได้เอาไว้ แล้วเราก็ทำงานกับคลาสตัวนั้นแทนที่จะทำงานกับของที่ควบคุมไม่ได้นั่นเอง ตามรูปด้านล่าง

    Design Pattern ที่มีลักษณะเป็น Wrapper Class มีหลายตัวเลย เช่น , Decorator ลองไปศึกษาต่อได้

    โดยปรกติ Wrapper Class จะมีหน้าที่เพียงแค่เรื่องเดียวคือควบคุมสิ่งที่มันดูแลอยู่ ดังนั้นเราก็จะมี Wrapper Class เอาไว้ดูแล API พรหัก โดยเฉพาะเลย ตามรูปด้านล่าง

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

    คราวนี้เวลาที่ (1) client ขอเข้าเว็บเมื่อไหร่ (2.1) เราก็จะเรียกใช้ตัว IWebApi มาทำงาน (2.2) ซึ่งตัว object จริงๆของมันคือ Wrapper (3) แต่ว่าเจ้า Wrapper มันไม่ได้ทำงานเอง มันจะคอยส่งไปให้ พรหัก API ต่างหาก (4) แล้วเมื่อได้ผลลัพท์กลับมา เจ้า Wrapper ก็จะคอยตรวจสอบ/แก้ไขผลลัพท์ให้เรียบร้อยก่อน (5) ค่อยส่งกลับไปให้ผู้ใช้ตามรูปนั่นเอง

    🤠 หากเราอยากเพิ่ม/ลดเงื่อนไขต่างๆก็จะมีผลกระทบแค่กับคลาสเดียวนั่นคือ Wrapper นั่นเอง ซึ่งก็จะตรงกับกฎของ เรียบร้อย

    🤠 จากที่ว่ามาก็จะทำให้เราสามารถ เพิ่มตัวที่อยากควบคุมอื่นๆเพิ่มเข้ามาได้เรื่อยๆ โดยไม่มีผลกระทบกับโค้ดเดิม ซึ่งตรงกับกฎของ เรียบร้อย และการแก้ไขเล็กๆน้อยๆก็จะไม่มีผลกระทบกับ Module หลัก ซึ่งตรงกับกฎ เช่นกัลล์

    🤠 แถมการทำแบบนี้ก็จะทำให้ Wrapper ของเราเป็นหนึ่งเดียวกันกับสิ่งที่มันต้องการควบคุม ซึ่งก็จะตรงกับกฎ อีกด้วย

    Liskov Substitution Principle (LSP) การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้เราระแวงในการใช้ subclass เสมอ เพราะไม่แน่ใจว่า subclass ที่เอามาใช้ จะสามารถทำงานได้ 100% แบบคลาสแม่นั่นเอง สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้

    😳 อุ๊ตะ!! พึ่งเห็นว่าลืมเปลี่ยนชื่อ Wrapper Class ซะได้ ดังนั้นเราก็เปลี่ยนมันเป็น Proxy เก๋ๆล้อกับสิ่งที่มันควบคุมอยู่ตามรูปด้านล่างนั่นเอง

    ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า Proxy Pattern เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏

    🤔 Proxy Pattern คือไย ?

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

    🤔 ประโยชน์จริงๆคือไย ?

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

    🔥 Virtual Proxy

    หากของที่เราจะเรียกใช้งานมันเปลืองทรัพยากร เช่น ไฟล์รูปมันใหญ่มากเสียเวลาโหลด ดังนั้นเราก็สามารถใช้ Proxy ให้มันจำรูปที่เคยโหลดมาเก็บไว้เป็น memory cache แล้วถ้าเรียกใช้เมื่อไหร่ก็จะได้ไม่ต้องโหลดใหม่ก็ได้ (ของที่เป็นตระกูล caching data เราควรทำ expiration ให้มันด้วยเสมอ) หรือ การสร้าง object บางตัวค่อนค่างกินเวลา แบบ Database Connection เราก็สามารถทำ cache เป็น connection pool ไว้ก่อนก็ได้

    🔥 Remote Proxy

    หากของที่เราจะเรียกใช้สามารถทำงานจากภายใน Local ได้ เช่น ของบางอย่างสามารถทำงานแบบง่ายๆได้จากตัว client เลยก็สามารถเขียนเป็น Proxy ให้มันทำงานในนั้นให้จบซะ ส่วนไหนทำไม่ได้ค่อยส่งมาที่ Server ก็ได้ เพราะของบางอย่าง client บางประเภทไม่สามารถเอา logic ไปเขียนไว้ในนั้นได้ เช่น ของที่มี sensitive logic ที่ไม่อยากให้คนอื่นเห็น

    🔥 Protective Proxy

    หากของที่เราจะเรียกใช้จะต้องตรวจสอบสิทธิในการเข้าถึงก่อน เช่น เราตั้งเงื่อนไขว่าคนใช้งานต้องอายุเกิน 18 ปีขึ้นไป แต่ตัว API อีกฝั่งไม่ได้ตรวจเรื่องนี้ให้ เราก็สามารถตรวจเงื่อนไขก่อนที่จะเรียกใช้งานได้นั่นเอง

    🔥 Smart Proxy

    เจ้าตัวนี้จะคล้ายๆกับ Remote Proxy จนบางทีเขาก็มองว่ามันคืออันเดียวกัน ซึ่งหน้าที่ของมันคือดูว่าของที่จะตอบกลับไปจริงๆแล้วควรเป็นอะไรนั่นเอง

    😎 Proxy Design

    การออกแบบ Proxy นั้นสามารถทำได้หลายวิธีเลย และไม่จำเป็นต้องทำตามที่ ดช.แมวน้ำ บอกมานะ เพียงแค่คนส่วนใหญ่นิยมใช้แบบไหนป๋มก็เลยเอามาเขียนเจ๋ยๆ ซึ่งปรกติเราจะเห็นการออกแบบ Proxy 2 วิธีตามด้านล่าง

    ข้อควรระวัง แบบที่ 1 และ แบบที่ 2 มันมีทั้งข้อดีข้อเสียคนละแบบกัน ดังนั้นก่อนเอาไปใช้ก็ชั่งน้ำหนักให้ดีก่อนนะกั๊ฟ แต่โดยปรกติตัวเลือกที่ 1 จะเหมาะสมและใช้งานได้ง่ายที่สุดนั่นเอง

    แบบที่ 1 - สร้างมาตรฐานขึ้นมา แล้วใช้ร่วมกัน

    👍 ข้อดี - ของทุกอย่างถูกแยกขาดออกจากกัน ทำให้เราแก้ไขได้โดยไม่มีผลกระทบกับอีกตัว

    👎 ข้อเสีย - เราก็ไม่สามารถเข้าถึงอีกฝั่งได้เยอะมากเท่าไหร่

    แบบที่ 2 - Inherited จากตัวที่อยากควบคุมตรงๆ

    👍 ข้อดี - เราสามารถใช้ความสามารถส่วนใหญ่ได้จากตัว base class เลย และ subclass ก็ได้อานิสงตามไปด้วย

    👎 ข้อเสีย - ถ้าแก้ไขโค้ดที่ base class ก็จะส่งผลกระทบถึง sub class ด้วย

    🎯 บทสรุป

    👍 ข้อดี

    การนำ Proxy Pattern มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง แถมยังสามารถควบคุมการทำงานอีกฝั่งได้ดั่งใจแม้ว่าเราจะไม่ได้เป็นคนเขียนอีกฝั่งก็ตาม ซึ่งส่วนใหญ่เราจะใช้ควบคุม 3rd party library และสุดท้ายโค้ดของเราถูกแยกหน้าที่ออกให้ดูแลเป็นของใครของมัน (Separation of Concerns)

    👎 ข้อเสีย

    เพิ่มความซับซ้อนโดยไม่จำเป็น เพราะการนำ Proxy ไปใช้ จะทำให้เราไม่สามารถทำงานกับ Source ได้ตรงๆ

    ข้อควรระวัง อย่านำ Proxy Pattern ไปใช้มั่วซั่ว เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะเรียกใช้จาก Source ได้ตรงๆ เราจะต้องทำผ่าน Adapter อีกทีหนึ่ง ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    OOP + Power of Design

    🧐 บทปิดท้ายแห่งการหักมุม! ที่จะเผยพลังที่แท้จริงของการออกแบบ

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

    แนะนำให้อ่านก่อนอ่านบทนี้ สำหรับใครยังไม่ได้อ่านบทความก่อนหน้าให้รีบไปอ่านด่วนจะได้เข้าใจบทความนี้มากยิ่งขึ้น ซึ่งสามารถไปอ่านได้จากลิงค์นี้เบย

    ☝️ Singleton Pattern

    แนวคิดในการสร้าง object ที่มีได้เพียงตัวเดียว

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

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช

    var a = new ObjectA();
    var b = new ObjectB();
    a.Ref = b;
    b.Ref = a;
    Character object2 = new Character();
    object2.Level = object1.Level;
    object2.Hat = object1.Hat;
    object2.Class = object1.Class;
    object2.HP = object1.HP;
    object2.SP = object1.SP;
    object2.Hat.Name = "Santa Hat";
    object2.HP = 55;
    public interface ICharacter { ... }
    
    ICharacter object1;
    ICharacter object2 = new ???
    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
            };
        }
    }
    ICharacter object1;
    ICharacter object2 = object1.Clone();
    object2.Hat.Name = "Santa Hat";
    เมื่อเราแก้ไขอะไรก็ตามผู้ใช้ต้องไม่รู้ตัวถึงการเปลี่ยนแปลงใดๆเลย
    Single-Responsibility Principle
    Open & Close Principle
    Dependency-Inversion Principle
    Adapter Pattern
    กดตรงนี้
    Adapter
    Single-Responsibility Principle
    Open & Close Principle
    Dependency-Inversion Principle
    Liskov Substitution Principle
    Liskov Substitution Principle (LSP)
    Mr.Saladpuk
    ช่องทางสนับสนุนค่าอาหารแมวน้ำกั๊ฟ 😘

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

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

    🔥 เก็บตก

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

    🧐 โจทย์ 05

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

    • นักดาป (Swordman) - เมื่อนั่งจนครบเวลาพลังชีวิตจะเพิ่มขึ้น 20 หน่วย

    • พระ (Acolyte) - เมื่อนั่งจนครับเวลา พลังชีวิตจะเพิ่มขึ้น 11 หน่วย

    • เด็กฝึกหัด (Novice) - ไม่ว่าจะนั่งแค่ไหนก็ตาม พลังชีวิตก็จะไม่เพิ่มเด็ดขาด

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

    🧒 แก้โจทย์

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

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

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

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

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

    🧐 โจทย์ 06

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

    🧒 แก้โจทย์

    การก็แค่เปลี่ยนเจ้าคลาสนั้นให้กลายเป็นสิ่งที่เรียกว่า abstract class ซะซิ หรือพูดง่ายๆคือเรามองว่าเจ้าคลาส Character นั้นมันเป็นแค่ concept เท่านั้น ไม่สามารถเอาไปใช้งานได้จริงๆยังไงล่ะ (อ่านเรื่อง abstract class ต่อได้จากบทความนี้ Abstract Class) ดังนั้นเราก็จะได้โค้ดออกมาเป็นแบบนี้ขอรับ

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

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

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

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

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

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

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

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

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

    • นักดาป (Swordman) - มีท่าโจมตีพิเศษ

    • พระ (Acolyte) - สามารถรักษาตัวเองและเพื่อนๆได้

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

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

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

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

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

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

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

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

    • นักดาป กับ อัศวิน - ใช้ Bash และ Magnum Break ได้

    • พระ กับ นักบวช - ใช้ Heal และ Divine Protection ได้

    • Crusader - ใช้ได้หมดเลย (Bash, Magnum Break, Heal และ Divine Protection)

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

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

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

    • นักดาป มี Bash และ Magnum Break

    • พระ มี Divine Protection

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

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

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

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

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

    คำเตือน 😤 Skills ยังไม่ได้ทำ Encapsulation นะ ทำให้ดูหลายครั้งแล้วลองไปหัดทำต่อเอาละกัน

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

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

    • ใช้ได้เลยไม่ต้องมีเป้าหมาย

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

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

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

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

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

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

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

    โอ้วววว เรียบร้อยงอดแงมตามท้องเรื่อง (จะได้ไปนอนซะที ฮ่าๆ) ซึ่งทั้งหมดที่ทำให้ดูนี้เราก็จะสามารถนำ Models ต่างๆไปประกอบกันจนสุดท้ายเราก็สร้าง ตัวละคร และ ศตรู ที่มี skill หลายๆแบบแตกต่างกันได้หมดละ โดยที่โค้ดของเราสามารถเพิ่มสิ่งใหม่ๆเข้าไปได้โดยที่เราไม่ต้องไปแก้ไขโค้ดเดิมนั่นเอง ซึ่งตรงกับหลักในการออกแบบพื้นฐานที่ชื่อว่า Open & Close Principle นั่นเอง

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

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

    • ตัวละครของเรา และ พวกศตรูแต่ละตัวมันจะมี skill ติดตัวมาไม่เหมือนกัน แล้วเราจะออกแบบยังไงให้รองรับของพวกนั้นกันดีนะ ?

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

    🎯 บทสรุป

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

    ตัวอย่างผมตกเรื่องไหน อยากให้เสริมเรื่องไหน หรือ ไม่เห็นด้วย สามารถแนะนำติชมได้หมดครับ ผมน้อมรับเอาไปปรับปรุงเสมอ เจอกันได้ที่ Facebook Mr.Saladpuk ครัช (จิ้มไปติดตามโลดจะได้ไม่พลาดอัพเดทใหม่ๆ)

    แนะนำให้อ่าน หลักในการออกแบบที่จะช่วยให้เราทำงานได้ง่ายขึ้น ในตอนที่เราเจอปัญหาคล้ายๆกับเคสตัวอย่างอันนี้นั่นคือ

    • ****Strategy Pattern****

    • ****Bridge Pattern****

    • ********

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

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

    👦 SOLID Design Principles

    📝 ลองเขียน OOP ดูดิ๊
    หมายเหตุ เนื้อหาของบทความนี้จะเน้นให้เข้าใจหลักการทำงานของ Design Patterns แต่ละตัว โดยใช้เกม Ragnarok เป็นการอธิบาย ซึ่งบางอย่างอาจจะไม่ตรงกับตัวเกมจริงๆนะขอรับ Gravity อย่ามาจับผมนะผมโดนแมวน้ำครอบงำ + รู้เท่าไม่ถึงการ + ผมเป็นคนดี + ผมมีลูกมีเมียมีสามีที่ต้องดูแล 😭

    🧐 โจทย์

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

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

    ผู้เล่นทุกคนเข้ามาดูได้ว่าสุขภาพของบอสเป็นยังไง ใกล้ถึงเวลาเกิดหรือยัง

    แล้วเราจะเขียนโค้ดออกมายังไงดี เพื่อให้มันสามารถตอบโจทย์ความต้องการแบบนี้ ได้อย่างไม่มีปัญหากันนะ ?

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

    จากที่ว่ามาก็น่าจะไม่มีอะไรยากชิมิ? ถ้าเราอยากให้บอสมันมีตัวเดียวเราก็แค่ไปสร้าง object ของบอสตัวนั้นขึ้นมา 1 ตัว แล้วเขียนเงื่อนไขลงไปว่าห้ามสร้างเกิน 1 ตัวนะจ๊ะ ตามโค้ดด้านล่างนี้ใช่ป่ะ

    ส่วนถ้าเราอยากให้ทุกคนสามารถเข้าถึง object ตัวนี้ได้จากที่ไหนก็ได้ เราก็จะให้มันเป็น static member ยังไงล่ะ แก้โค้ดแพร๊บ

    ไหนลองตรวจดูดิ๊ว่าตอนที่ไปเอา object นี้ออกมา มันจะเป็น object เดียวกันหรือเปล่านะ

    ผลลัพท์ True

    ซึ่งดูเหมือนว่าเราจะแก้โจทย์นี้เสร็จแล้วใช่ไหม เพราะใครอยากได้ object ของบอสตัวนี้ก็แค่เรียกผ่านเมธอด GetDetardeurus() ก็จะได้ object ตัวเดียวกันไปใช้งาน แถมเมื่อมันเป็น static ใครจะมาเรียกใช้งานก็สามารถทำได้เลยนั่นเอง ตามรูปด้านล่าง

    เสียใจด้วยนะมันไม่ได้ง่ายแบบนั้นหรอก แม้ว่าเราจะได้ object เดียวกันกลับมาเสมอก็จริง แต่เราจะต้องเรียกใช้งานผ่าน EventBoss.GetDetadeurus() เท่านั้น ... แล้วมันจะเกิดอะไรขึ้นถ้ามีคนอื่นดันไปสร้าง object นั้นขึ้นมาตรงๆด้วยคำสั่ง new กันล่ะ ?

    ผลลัพท์ False

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

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

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

    ถ้าเราวิเคราะห์ปัญหาดีๆ สาเหตุที่แท้จริงของปัญหาในตอนนี้คือ ใครอยากสร้าง object นี้ก็สร้างได้เลย เพียงแค่ใช้คำสั่ง new นั่นเอง (เพราะมันคือพื้นฐานของ class)

    แต่ถ้าเราลองไล่ลำดับการทำงานของ class จริงๆเราจะพบว่า เมื่อใช้คำสั่ง new ปุ๊ป สิ่งแรกที่มันจะทำก็คือ มันจะเรียกใช้ Constructor เป็นลำดับแรก ซึ่งโดยปรกติ default constructor จะเป็น public นั่นเอง เลยทำให้ใครอยากสร้าง object ก็สามารถสร้างได้เลย ตามรูป

    แนะนำให้อ่าน สำหรับใครที่ลืมหรืออยากทบทวนการทำงานของ Class & Constructor ก็สามารถเข้าไปดูได้จากลิงค์ตัวนี้เลยครัช มารู้จักกับ Constructor กันบ้าง

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

    ไม่เชื่อลองไปเขียนโค้ดดูดิ มันจะสร้าง object จากคลาสนั้นไม่ได้เลย

    เพียงแค่นี้ก็ไม่มีคนสร้าง object จากคลาสพิเศษของเราได้ละ ... แต่ก็เกิดคำถามใหม่ว่า ถ้าใช้คำสั่ง new สร้าง object ไม่ได้ แล้วเราจะเอา object ของมันออกมาได้ยังไงกันล่ะ ?

    🔥 แก้ไขปัญหา

    เมื่อเราคิดต่อดูอีกที การที่เราเปลี่ยน constructor เป็น private มันจะทำให้ภายนอกไม่สามารถเข้าถึงได้ แต่ว่า ภายในยังสามารถเข้าถึงได้ตามปรกติ นั่นเอง

    ดังนั้นเราก็จะสร้าง object มันจากภายในคลาสมันเองยังไงล่ะ!! ตามรูปเลย อะชึบอะชึบ

    เห็นไหมว่าภายในก็ยังใช้งาน constructor ตัวเองได้ตามปรกติ ดังนั้นก็จะเหลือแค่ จะให้ภายนอกเข้ามาใช้งาน object ยังไงนั่นเอง ... ซึ่งตรงนี้มันก็ไม่ยากอีกต่อไป เพราะในโค้ดแรกสุดที่เราออกแบบไว้นั้นทำไว้เรียบร้อยแล้ว โดยการเปิดให้ภายนอกเข้าถึงได้ผ่าน static member นั่นเอง ดังนั้นเราก็จะได้โค้ดออกมาเป็นแบบนี้

    จบเรียบร้อยแล้ว เพียงเท่านี้เราก็จะได้คลาสพิเศษที่ทั้งโปรแกรมของเรามี object ได้เพียง 1 ตัวเท่านั้น (เพราะภายนอกมันสร้าง object ตัวนี้ไม่ได้) แถมยังมีช่องทางให้เข้าถึงได้จากทุกที่อีกด้วย (ผ่านทาง static นั่นเอง)

    🤔 Singleton คือไย ?

    🔥 จุดกำเหนิด

    ในบางครั้งเราต้องการให้ object ถูกจำกัดจำนวนครั้งที่สร้างได้ และ ต้องการให้มีช่องทางที่เข้าถึง object เหล่านั้นได้ง่ายๆ

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

    เราสามารถจำกัดการสร้าง object ได้ตามที่เราต้องการ และมีช่องทางเข้าถึงแบบเจ้า object พวกนั้นแบบ Global

    🔥 วิธีการใช้

    ถ้าเราอยากให้คลาสไหนถูกจำกัดจำนวนในการสร้าง object เราก็แค่ ห้ามให้คนอื่นสร้าง object ได้ตามใจด้วยการทำให้คลาสนั้นเป็น private constructor ซะ ส่วนเงื่อนไขและการสร้าง object ตัวนั้นก็จะถูกจัดการอยู่ภายในคลาสตัวนั้นเอง และเปิดช่องทางให้คนอื่นเข้าถึงผ่าน static member ... เพียงแค่นี้เราก็จะได้คลาสที่เป็น Singleton เรียบร้อยแบ้ว

    ไหนลองเอาที่เราออกแบบมาเทียบกันดูดิ๊ ... เหมือนกันเปี๊ยบเบย

    🤠 เทคนิค

    วิธีการนำ Singleton ไปใช้นั้นมีหลายแบบเลย ซึ่งแต่ละแบบก็จะมีข้อดีข้อเสียที่ต่างกันด้วย ดังนั้นเราลองมาดูกันหน่อยว่ามันมีอะไรกันบ้าง

    🔥 Lazy Initialization

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

    ซึ่งจากโค้ดจะเห็นว่า ถ้าไม่เคยมีใครเรียกใช้เมธอด GetInstance() นั่นก็หมายความว่า object ตัวนี้ก็จะไม่เคยถูกสร้างเลยนั่นเอง และ ถ้ามันเคยถูกสร้างแล้ว มันก็จะไม่ต้องไปสร้างใหม่อีกเลย

    👍 ข้อดี

    • ไม่เสียเวลาในการสร้าง

    • ไม่เปลือง memory

    👎 ข้อเสีย

    • ตอนจะสร้างถ้ามันต้องไปทำงานนั่นนู่นนี่เยอะ มันจะทำให้โปรแกรมดูหน่วงๆหน่อยนึง จนกว่าจะสร้างเสร็จ

    • มีปัญหากับการทำงานแบบ Multi-Threading เพราะมันมีโอกาสเข้าไปสร้าง instance พร้อมกัน

    🔥 Early Initialization

    เป็นด้านตรงกันข้ามกับ Lazy Initialization เพราะมันจะสร้าง object ทิ้งไว้เลยตั้งแต่แรก ซึ่งเป็นโค้ดตามตัวอย่างแรกๆที่เราเขียนไว้ด้านบนเลย

    👍 ข้อดี

    • หลังจากที่มันสร้างเสร็จมันจะพร้อมใช้งานทันที ดังนั้นตอนที่ถูกเรียกใช้ มันจะไม่รู้สึกหน่วงๆ

    • ไม่มีปัญหากับ Multi-Threading

    👎 ข้อเสีย

    • เสียเวลาในการสร้าง (ไปหน่วงตอนเปิดแอพแทน)

    • เปลือง memory เพราะ object นั้นอาจไม่เคยถูกเรียกใช้เลยก็ได้ แต่มันถูกสร้างไว้แล้ว

    🔥 Bindable Object

    โดยปรกติเวลาที่เราทำงานร่วมกับ object ที่มีการเรียกเอาไปใช้งานนั้น เราจะไม่ค่อยสร้างเป็นเมธอดสักเท่าไหร่ เพราะมันเอาไปใช้ในการทำ Data Binding ไม่ได้ (เช่นพวก MVC, MVVM) ดังนั้นเพื่อเป็นการแก้ปัญหาเราจะนิยมไปสร้างเป็น Property มากกว่านั่นเอง

    👍 ข้อดี

    • นำไปใช้ในการ Binding ได้เลย (1-2 ways ได้หมด)

    👎 ข้อเสีย

    • บางภาษาอาจจะไม่เหมาะสมทางเทคนิค

    🥴 ข้อผิดพลาดที่เจอบ่อยๆ

    ⛔ ใช้ static class แทน

    การใช้ static class แทนการทำ Singleton pattern นั้นจริงๆก็สามารถทำได้นะ ถ้าเราสามารถคุมการทำงานมันได้ แต่มันจะง่ายกว่าไหมเพียงแค่เปลี่ยนมันเป็น Singleton Pattern แล้วดูแลมันเป็น object ธรรมดาไปเลย ?

    ⛔ ไม่ใช้ private constructor

    ถ้ามาคิดถึง Access Modifier จริงๆแล้วก็มีอีกหลายตัวนะที่ใช้แทน private ได้ เช่น protected (internal ยังไม่สมควรเพราะมันถูกสร้างได้จาก internal namespace นั่นเอง) แต่ถามว่ามันสมควรใช้ของพวกนั้นแทน private ไหม คำตอบคือไม่สมควร เพราะเรามีความตั้งใจที่อยากจะให้มันถูกควบคุมดูแลได้จากที่เดียวอยู่แล้ว ดังนั้นมันไม่ควรมีที่ไหนเข้ามาแก้ไขได้นอกจากตัวเองอีก + การทำ sub class จาก singleton จะมีปัญหาอื่นๆตามมาอีก

    🎯 บทสรุป

    👍 ข้อดี

    • ช่วยให้เราสามารถควบคุมการสร้าง object ได้

    • มีช่องทางให้เข้าถึงแบบ Global

    • ซ่อนความวุ่นวายในการสร้าง object

    • ถ้าการสร้าง object มีการเปลี่ยนแปลง ก็สามารถแก้ได้จากจุดเดียว

    👎 ข้อเสีย

    • ยากต่อการจัดการกับ Life cycle ของมัน

    • มีปัญหากับการเขียนเทส

    • มีปัญหากับ Multi-Threading ถ้าไม่จัดการให้ดี

    🤙 ทางเลือก

    เราสามารถนำ Framework พวก Dependency Injection (DI) เข้ามาใช้แทนได้นะจ๊ะ โค้ดกระชับหลับสบายเต็มตื่นด้วย

    ข้อควรระวัง อย่านำ Singleton Pattern ไปใช้มั่วซั่ว เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะใช้คำสั่ง new แบบปรกติ ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย Mr.Saladpuk และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    Creational Patterns
    👦 Design Patterns

    Adapter Pattern

    แนวคิดในการเปลี่ยนสิ่งที่ทำงานร่วมกันได้ยาก มาทำงานร่วมกันได้ง่ายๆ 😘

    เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า ผู้เชื่อมสัมพันธ์ ละกัน ซึ่งมันอยู่ในกลุ่มของ 🧱 Structural Patterns โดยเจ้าตัวนี้จะมาช่วยแก้ปัญหาเมื่อ เรามีของ 2 อย่างที่ทำงานด้วยกันได้ค่อนข้างยาก แต่เราก็อยากให้มันทำงานด้วยกันได้โดยไม่ทำให้โค้ดเราซับซ้อนเกินไป พูดแล้วก็ งงๆ ไปดูโจทย์ของเรากันเลยดีกว่าจะได้เข้าใจได้เร็วขึ้น

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช 👦 Design Patterns

    หมายเหตุ เนื้อหาของบทความนี้จะเน้นให้เข้าใจหลักการทำงานของ Design Patterns แต่ละตัว โดยภาพจากเกม Ragnarok เป็นการอธิบาย ซึ่งหลายๆอย่างนั้นมโนขึ้นมาเพื่อความสนุก และทำให้เข้าใจเนื้อหาได้ง่าย Gravity อย่ามาจับผมนะผมโดนแมวน้ำครอบงำ + รู้เท่าไม่ถึงการ + ผมเป็นคนดี + ผมมีลูกมีเมียมีสามีที่ต้องดูแล 😭

    🧐 โจทย์

    สมมุติว่าเรารับงานเว็บทายผลบอลทุกคู่ทั่วโลกเพื่อความบันเทิงมา (ไร้ซึ่งการพนันใดๆทั้งสิ้น 🤐) โดยเมื่อบอลแข่งเสร็จ ตัวเว็บของเราก็ต้องไปดึงผลการแข่งขันจาก Web API ชั้นนำ 2 แห่งนั่นคือ LongOnGoal, LifeScore ตามรูปด้านล่าง

    🤔 ทำไมต้องดึงมาจากหลายเว็บเหรอ? ก็บางเว็บมีผลการแข่งเฉพาะคู่เล็กๆ บางเว็บมีผลเฉพาะคู่ใหญ่ๆ และบางทีเราก็อาจจะต้องตรวจสอบว่าผลมันตรงกันหรือเปล่า เพื่อที่จะได้จ่าย... เอ้ย ให้แต้มคนที่ทายถูกได้นั่นเอง 😅 (ส่วนแต้มต่อก็ ... ช่างมันเถอะ 🤐)

    เท่าที่อ่านๆมาก็เหมือนจะง่ายๆเนอะ แค่ไปดึง API จากเว็บ 2 ตัวมาก็จบแล้วชิมิ ไหนลองไปดู API สำหรับ ดึงผลคะแนน ของเขาดูดิ๊

    🤯 บรึ๊ม!! แม้ว่าจะเป็นเว็บผลคะแนนบอลเหมือนกันแต่ API เรียกใช้ไม่เหมือนกัน, parameters ต่างกัน, models ต่างกัน แล้วแบบนี้เราจะเขียนโค้ดยังไงดีเนี่ย? 😵

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

    อย่าไปคิดเยอะดิ ก็เขียนๆมันไปเลยจะยากอะไร อย่างแรกเลยก็เขียนไปดึงข้อมูลกับ LongOnGoalAPI มาเก็บไว้ แล้วก็ดึงจาก LifeScoreAPI มา สุดท้ายก็เอา 2 ตัวนี้มาเทียบกันก็จบละนิ ป๊ะโถ่วววว

    🤔 ก็เหมือนจะง่ายนะ แต่เราลืมอะไรไปป่ะว่า model ที่ได้กลับมามันไม่เหมือนกัน แล้วจะเปรียบเทียบยังไงอ่ะ?

    😎 งั้นก็ขอเขียนโค้ดแปลง model ทั้ง 2 ตัวไว้ตรงนี้เลยละกัน จะได้เทียบกันได้ง่ายๆ ตามด้านล่าง

    จากโค้ดด้านบนก็ไม่ได้ทำอะไรผิดนะ แก้โจทย์ที่ของเราได้ตามปรกติเลย แต่มันจะเกิดอะไรขึ้นถ้า เราเพิ่ม Web API ตัวที่ 3, 4, 5 ... เข้ามาล่ะ? เราก็ต้องไปไล่แก้โค้ดใหม่ทุกครั้งอะดิ แถมยิ่งแก้ยิ่งพันกันเป็นสปาเก็ตตี้ขึ้นไปเรื่อย 😨

    เพราะโค้ดของเรามันมีหลายอย่างที่ไม่ตรงหลักในการออกแบบที่ดี เช่น SRP, OCP, DIP ยังไงล่ะ

    Single-Responsibility Principle (SRP) การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ เวลาที่ Requirement เปลี่ยนมาทีนึง มันก็จมีโอกาสสูงมากที่การเปลี่ยนนั้นมันจะไปกระทบเจ้าสิ่งนั้น ทำให้เราต้องแก้ไขมัน ซึ่งผองเพื่อนอื่นๆที่มันดูแลอยู่นั้นไม่ได้เกี่ยวข้องเลยก็มีผลกระทบด้วยนั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย

    Open & Close Principle (OCP) การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้

    Dependency-Inversion Principle (DIP) การละเมิดกฎข้อนี้จะทำให้ module หลักต้องถูกแก้ไขบ่อยๆ เมื่อตัวที่ทำงานตัวเล็กตัวน้อยมีการเปลี่ยนแปลง แม้จะเปลี่ยนเพียงแค่เล็กน้อยก็ตาม

    🥴 อาวล่ะแม้ว่ามันจะมีปัญหาในอนาคตก็ตาม แต่อย่างน้อยเราก็ได้เห็นตัวอย่างในการบ้าพลัง เห็นโจทย์แล้วเมากาวกระโดดลงโค้ดเลย ผลก็คือ Design แบบกาวๆเหล่านั้นจะมีปัญหาในอนาคตนั่นเอง งั้นเดี๋ยวลองวางสมอง แล้วเติมน้ำหอมกันหน่อย เพื่อวิเคราะห์ปัญหาแล้วหาทางแก้ไขกันต่อ

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

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

    โจทย์ในรอบนี้ไม่ได้ซับซ้อนอะไรเลย แต่ความยากของมันกลับไปตกอยู่ที่ งานที่ต้องเชื่อมกับคนอื่น หรือตัว Web API ต่างๆเพราะ มันเป็นสิ่งที่เราควบคุมไม่ได้ว่าเขาจะออกแบบมายังไง หรือ เขาจะเปลี่ยนแปลงแก้ไขเมื่อไหร่

    🐲 ควบคุมไม่ได้ เมื่อมีของที่เราควบคุมไม่ได้อยู่ในระบบของเราความบรรลัยก็จะบังเกิด เพราะ วันดีคืนดีอยู่ๆของพวกนั้นเกิดใช้งานไม่ได้ หรือพฤติกรรมมันเปลี่ยนขึ้นมา ต่อให้โค้ดฝั่งเราเขียนดีแค่ไหนก็ตามก็ย่อมมีผลกระทบตามมา หลายคนอาจจะคิดว่าเป็นของไกลตัวโปรเจคเราไม่มีของพวกนั้นหรอก งั้นแมวน้ำถามกลับว่า โปรเจคเราได้ใช้ Library ของคนอื่นป่ะ? ซึ่งของพวกนั้นดูเหมือนจะไม่มีพิษมีภัยอะไร แต่จริงๆมันก็คือของที่เราควบคุมไม่ได้แบบหนึ่งเหมือนกัน ลองศึกษา Case Study ด้านล่างต่อละกันถ้าสนใจ

    Case Study มี Library หลายตัวที่แมวน้ำใช้มาหลายปีก็ไม่มีปัญหาอะไร เช่น , แต่พอมาถึงวันนึงที่เราต้องอัพเกรดเวอร์ชั่นปุ๊ป สิ่งที่เกิดขึ้นกับ SimpleInjector นั้นดูเหมือนไม่มีอะไรโค้ด compile ได้ไม่มีปัญหาแต่พฤติกรรมของมันเปลี่ยนไป ทำให้ life-cycle ของแอพผิดปรกติ นั่งหากันยกใหญ่ ส่วนเจ้า SendGrid ก็แสบไม่แพ้กัน พี่แกเล่นลบ core interface หลักทิ้ง แล้วไปขึ้น core interface ใหม่โดยไม่เหลืออันเดิมให้ใช้เลย (ปรกติเขาจะไม่ลบทิ้ง แต่จะใส่ obsolete attribute ไว้) ทำให้เราต้องมานั่งเสียเวลา re-implement ใหม่ หรือ Library บางตัวออกอัพเดทใหม่ก็มี bugs แถมมาด้วย 😒 แต่ทั้งหมดจะไปโทษเขาก็ไม่ได้หรอก เราในฐานะ Developer ต้องตรวจของที่จะเอามาใช้งานใน production ก่อนเสมอแหละ (อย่าให้ผู้ใช้มาเป็นเทสเตอร์ให้เราเลย 😂)

    🔥 แก้ไขปัญหา

    เมื่อเรามีของที่เราควบคุมไม่ได้ เราต้องเปลี่ยนเป็นสิ่งที่เราควบคุมได้ก่อน เพราะไม่อย่างนั้นสนุกแน่ ซึ่งการที่เราจะไปควบคุมของอื่นๆมีหลายวิธีเลย ซึ่งวิธีง่ายสุดคือการทำ Wrapper Class โดยเราจะสร้างคลาสขึ้นมาครอบของที่ควบคุมไม่ได้เอาไว้ แล้วเราก็ทำงานกับคลาสตัวนั้นแทนที่จะทำงานกับของที่ควบคุมไม่ได้นั่นเอง ตามรูปด้านล่าง

    Design Pattern ที่มีลักษณะเป็น Wrapper Class มีหลายตัวเลย เช่น , Decorator ลองไปศึกษาต่อได้

    โดยปรกติ Wrapper Class จะมีหน้าที่เพียงแค่เรื่องเดียวคือควบคุมสิ่งที่มันดูแลอยู่ ดังนั้นเรื่องการจัดการ Web API ทั้ง 2 ตัวนั้น เราก็จะมี Wrapper Class เอาไว้ดูแลมันโดยเฉพาะเลย ตามรูปด้านล่าง

    ถัดมาเรื่องการใช้ Web API ทั้ง 2 ตัว แม้ว่ามันจะต่างกันคนละโลกแค่ไหนก็ตาม แต่ถ้าลองคิดดูดีๆเราก็จะพบว่า ของที่เราอยากได้จากมันจริงๆนั้นคือคะแนนการแข่งขันระหว่าง 2 ทีม ดังนั้นจริงๆเราไม่ต้องสนใจด้วยซ้ำว่า API พวกนั้นจะส่ง model หน้าตาแบบไหนกลับมา ขอแค่เราสามารถแปลง model พวกนั้นให้กลายเป็นของที่เราสนใจได้ก็พอ ตามรูปด้านล่าง

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

    พอมันกลายเป็นแบบรูปด้านบนปุ๊ป เราก็จะพบว่า Wrapper ของเราไม่มีความต่างละ ดังนั้นเราก็สามารถจัดการมันในรูปแบบของ interface ได้เลย ตามรูปด้านล่าง

    🤠 จากที่ว่ามาทั้งหมดปัญหาเรื่อง เวลาของที่เราควบคุมไม่ได้มีการเปลี่ยนแปลง มันก็จะกระทบแค่ Wrapper ที่ดูแลสิ่งนั้นอยู่เท่านั้น เพราะตัว Wrapper รับผิดชอบการดูแลไปแล้วนั่นเอง ซึ่งก็จะตรงกับกฎของ เรียบร้อย

    🤠 หรือเราจะไปดึงข้อมูลจาก Web API อื่นๆ โค้ดเดิมก็ไม่มีผลกระทบอะไรเลย เพราะเราก็แค่เพิ่ม Adapter ตัวใหม่เข้าไป ซึ่งตรงกับกฎของ เรียบร้อย

    🤠 และสุดท้ายต่อให้เราจะเลิกใช้ Web API ตัวไหนไป หรือเรามีการแก้ไข Web API นิดๆหน่อยก็จะไม่มีผลกระทบกับ module หลักที่เรียกใช้ Adapter เหล่านี้แล้ว ซึ่งก็ตรงกับกฎ เช่นกัลล์

    ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า Adapter Pattern เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏

    🤔 Adapter Pattern คือไย ?

    มันคือ แนวคิดในการแก้ปัญหาตอนที่เรามีของ 2 อย่างที่ทำงานร่วมกันไม่ได้หรือทำได้ยาก แต่เราก็อยากให้มันทำงานด้วยกันได้โดยไม่ทำให้โค้ดเราซับซ้อนเกินไป ซึ่งในโลกความเป็นจริงเราก็จะเห็น Adapter Pattern ได้จากที่ชาร์จโทรศัพท์ไง เพราะเครื่องบางยี่ห้อจะรับเฉพาะ Lightning บางยี่ห้อรับเฉพาะ Micro USB บางอันเป็น USB-C ดังนั้นหน้าที่ของ Adapter Pattern ก็คือการแปลง ของที่เข้ากันไม่ได้ ให้ลงรอยกันได้นั่นเอง ตามรูปด้านล่าง

    ดังนั้นเมื่อไหร่ก็ตามที่เราเจอปัญหาเข้ากันไม่ได้ ให้รู้ได้เลยว่า Adapter Pattern อาจจะเป็นหนึ่งในทางออกของเราก็ได้

    💡 หลักในการคิด

    เมื่อไหร่ก็ตามที่เราเจอของ 2 อย่างที่มีการทำงานแตกต่างกัน เราก็แค่สร้างมาตรฐานใหม่ที่จะเอามากำกับดูแลของพวกนั้น ให้มันทำงานตรงตามสิ่งที่เราอยากได้ก็พอ ซึ่งในตัวอย่างของเราได้สร้างมาตรฐานใหม่ที่ชื่อว่า IFootballAdapter เอาไว้กำกับดูแลการทำงานของ Web API ทั้งหลายนั่นเอง

    หมายเหตุ โดยปรกติตัวมาตรฐานใหม่ที่เราสร้างขึ้นมาเราจะเรียกมันว่า Adapter ส่วนตัวที่ถูก adapter ดูแลอยู่ เราจะเรียกมันว่า Adaptee

    🤠 การนำ Adapter ไปใช้งานนั้น สามารถออกแบบได้เยอะมาก ไม่ได้จำกัดว่าจะต้องเหมือกับในตัวอย่าง เช่นการทำ Class Adapter Pattern, Object Adapter Pattern

    เกร็ดความรู้ Class Adapter Pattern นั้นถูกออกแบบมาสำหรับภาษาที่รองรับการทำ multi-inheritance ก็จริง แต่ก็ไม่ได้หมายความว่าภาษาที่ไม่รองรับ multi-inheritance จะทำไม่ได้นะ เพราะเราสามารถทำ implement interface ได้หลายตัว

    😎 Perfect Adapter

    เมื่อเราสามารถแปลงข้อมูลจากฝั่ง A ไปหาฝั่ง B ได้ ในทฤษฎีของ Adapter Pattern นั้นก็ยังบอกอีกว่า เราก็ควรทำให้เจ้า Adapter สามารถแปลงข้อมูลกลับจากฝั่ง B ไปหาฝั่ง A ได้ด้วยเช่นกัน

    🎯 บทสรุป

    👍 ข้อดี

    การนำ Adapter Pattern มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง เพราะเราไม่ต้องไปวุ่นวายกับ Concrete class ที่อาจเปลี่ยนแปลงภายหลังได้ แถมจะเพิ่มจะลบ Adapter ตัวไหนก็สามารถทำได้เลยอีกด้วย

    👎 ข้อเสีย

    เพิ่มความซับซ้อนโดยไม่จำเป็น เพราะการนำ Adapter ไปใช้ จะทำให้เราไม่สามารถทำงานกับ Source ได้ตรงๆ

    ข้อควรระวัง อย่านำ Adapter Pattern ไปใช้มั่วซั่ว เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะเรียกใช้จาก Source ได้ตรงๆ เราจะต้องทำผ่าน Adapter อีกทีหนึ่ง ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    Inheritance

    🤔 มันคืออะไร ?

    คำว่า Inheritance มีใช้อยู่ในหลายวงการเลย แต่ในวงการซอฟต์แวร์ใน Wikipedia ถูกเขียนไว้ว่า

    Inheritance is the mechanism of basing an or upon another object () or class (), retaining similar implementation. Also defined as deriving new classes () from existing ones (super class or ) and forming them into a hierarchy of classes. In most class-based object-oriented languages, an object created through inheritance (a "child object") acquires all the properties and behaviors of the parent object (except:

    C# version 8.0

    �� ภาษา C# เวอร์ชั่น 8.0 มีอะไรใหม่ๆบ้าง

    หลายๆคนอาจจะเคยเห็นในอินเตอร์เน็ทเขาเขียนภาษา C# ในรูปแบบที่เราไม่เคยเห็นมาก่อน แล้วพอเราไปทำตามก็เขียนแบบนั้นไม่ได้ นั่นเป็นเพราะภาษา C# ในแต่ละเวอร์ชั่นเขาได้มีการเพิ่มความสามารถใหม่ๆเข้าไปเสมอนั่นเอง แล้วถ้าเราอยากใช้ความสามารถใหม่ๆพวกนั้น เราก็ต้องทำการอัพเดทตัว C# เวอร์ชั่นของเราด้วยถึงจะใช้ได้ ดังนั้นในรอบนี้เราจะมาดูกันว่า C# เวอร์ชั้น 8.0 จะมีความสามารถอะไรใหม่ๆมาให้เราลองเล่นกันบ้าง

    🔥 Readonly members

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

    จากโค้ดด้านบนถ้าเราไม่อยากให้ ToString()

    public class ApiRequestHandler
    {
        private พรหัก api;
        public ApiRequestHandler(พรหัก api)
        {
            this.api = api;
        }
        
        public Content(string key)
        {
            var response = api.Content(key);
            // ตรวจสอบหรือแก้ไข response เพื่อให้เป็นแบบที่เราต้องการ
            return response;
        }
    }
    public class Character
    {
        public virtual void Sit()
        {
            Console.WriteLine("Sit");
        }
    }
    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");
        }
    }
    public abstract class Character
    {
        ...
    }
    Character character1 = new Swordman();
    character1.SuperAttack();   // error เพราะคลาสแม่ไม่รู้จัก
    
    Character character2 = new Acolyte();
    character2.Heal();          // error เพราะคลาสแม่ไม่รู้จัก
    public class Skill
    {
        public string Name { get; set; }
        public int EffectOnHP { get; set; }
        public int EffectOnAttack { get; set; }
        public bool IsRequiredTarget { get; set; }
    }
    public abstract class Character
    {
        public IEnumerable<Skill> Skills { get; set; }
    
        ...
    }
    public abstract class Character
    {
        public void Spell(Skill skill) { }
        public void Spell(Skill skill, Character target) { }
    
        ...
    }
    public class EventBoss
    {
        private Detardeurus boss = new Detardeurus();
    
        public Detardeurus GetDetardeurus()
        {
            return boss;
        }
    }
    public class EventBoss
    {
        private static Detardeurus boss = new Detardeurus();
    
        public static Detardeurus GetDetardeurus()
        {
            return boss;
        }
    }
    static void Main(string[] args)
    {
        var boss1 = EventBoss.GetDetardeurus();
        var boss2 = EventBoss.GetDetardeurus();
    
        Console.WriteLine(boss1 == boss2);
    }
    var myboss1 = new Detardeurus();
    var myboss2 = new Detardeurus();
    
    Console.WriteLine(myboss1 == myboss2);
    public class Detardeurus
    {
        private Detardeurus()
        {
        }
    }
    new Detardeurus(); // ERROR
    public class Detardeurus
    {
        private Detardeurus instance;
    
        private Detardeurus()
        {
            instance = new Detardeurus();
        }
    }
    public class Detardeurus
    {
        private static Detardeurus instance;
    
        private Detardeurus()
        {
            instance = new Detardeurus();
        }
    
        public static Detardeurus GetInstance()
        {
            return instance;
        }
    }
    public class Singleton
    {
        private static Singleton instance;
    
        private Singleton() { }
    
        public static Singleton GetInstance()
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }
    public class Singleton
    {
        private static Singleton instance = new Singleton();
    
        public static Singleton GetInstance()
        {
            return instance;
        }
    
        private Singleton() { }
    }
    public class Singleton
    {
        private static Singleton instance;
    
        public static Singleton Instance
            => instance;
    
        protected Singleton()
        {
            instance = new Singleton();
        }
    }
    , destructor,
    and
    of the base class). Inheritance allows programmers to create classes that are built upon existing classes,
    to specify a new implementation while maintaining the same behaviors (
    ), to
    and to independently extend original software via public classes and interfaces. The relationships of objects or classes through inheritance give rise to a
    . Inheritance was invented in 1969 for
    .

    😑 แค่อ่านก็ปวดกบาลละ แต่ก็ถอดหัวใจสำคัญของมันออกมาได้ว่า

    Inheritance คือการสืบทอดคุณสมบัติ จาก Model A ไปยัง Model อื่นๆได้ ซึ่งมันจะช่วยให้เราเพิ่มความสามารถใหม่ๆเข้าไปได้โดยที่ไม่ต้องไปยุ่งกับโค้ดเก่าที่เคยเขียนไว้

    🤨 ก็ยัง งง อยู่ดีขอตัวอย่างหน่อย

    😢 ปัญหา

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

    ซึ่งบัญชีมันไม่ได้มีแค่ประเภทเดียวนะ เช่น บัญชีกระแสรายวัน ซึ่งมันก็ต้องเก็บข้อมูล เงินในบัญชี, เจ้าของบัญชี, ฝากเงินเข้าบัญชี ได้เหมือนกันด้วย ดังนั้นเราก็จะสร้าง Model ขึ้นมาอีกตัวหน้าตาประมาณนี้

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

    Needless Repetition นี่คือตัวอย่างการเขียนโค้ดที่ไม่ดี นั่นคือการ copy งานที่เหมือนๆกันไปใช้ในแต่ละที่ ซึ่งหลักในการออกแบบที่ดี เราไม่ควรจะทำงานเดิมซ้ำ Don't Repeat Yourself (DRY) ซึ่งเพื่อนๆสามารถศึกษาเรื่อง Bad Code ได้จากบทความ 👶 Code Smells

    😄 วิธีแก้ปัญหา

    เราสามารถนำหลักการ Inheritance มาช่วยในเรื่องนี้ได้ โดยการสร้าง Model ต้นแบบ ขึ้นมา แล้วทำการย้ายของที่ซ้ำกันไปไว้ในตัวต้นแบบตัวนั้น ซึ่งในตัวอย่างของที่เรากำลังทำอยู่มันคือ บัญชีธนาคาร ดังนั้นเราเลยสร้างคลาส BankAccount ขึ้นมาใหม่

    แล้วทำการย้ายของที่มันซ้ำกันจาก บัญชีออมทรัพย์ กับ **บัญชีกระแสรายวัน มาไว้ในตัวต้นแบบซะ

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

    เพียงเท่านี้ บัญชีออมทรัพย์ และ บัญชีกระแสรายวัน ก็จะมีความสามารถทุกอย่างที่ตัวต้นแบบมีนั่นเอง ดังนั้นลองเขียนโค้ดเล่นกับบัญชีทั้ง 2 ตัวของเราดู

    Output (Saving) Saladpuk, has THB 500. (Current) Saladpuk, has THB 700.

    😵 Inheritance คือไรกันแน่ ?

    Inheritance มันมีหลายเรื่องอยู่ในนั้นเยอะเลย ดังนั้นมาค่อยๆทำความเข้าใจกันทีละเรื่องก่อนละกันนะ โดยสิ่งที่เราต้องรู้สิ่งแรกคือ

    Inheritance (การสืบทอด)

    ✨ เราสามารถนำคลาสที่มีอยู่แล้ว มาต่อยอดความสามารถให้กับคลาสใหม่ได้

    เราได้เห็นตัวอย่างโค้ดด้านบนไปแล้วว่า บัญชีออมทรัพย์ และ บัญชีกระแสรายวัน ได้ต่อยอดความสามารถจาก BankAccount มา เลยทำให้บัญชีทั้ง 2 ประเภทมี OwnerName, Balance และ Deposit() ทั้งๆที่ตัวมันเองไม่ได้มีโค้ดอะไรอยู่ด้านในเลย ซึ่งสิ่งนี้เราเรียกว่า การสืบทอด หรือ Inheritance นั่นเอง

    Base class (คลาสแม่)

    โดยคลาสที่เป็นต้นแบบเราเรียกมันว่า Base class (บางตำราจะเรียกว่า Super class, Parent class บลาๆ) ซึ่งในตัวอย่างนี้คลาส BankAccount เป็น base class ของ SavingAccount และ CurrentAccount นั่นเอง

    Sub class (คลาสลูก)

    ส่วนคลาสที่ไปสืบทอดความสามารถจากคนอื่นเราเรียกมันว่า Sub Class (บางตำราเรียกว่า Derived class, Child class บลาๆ) ซึ่งในตัวอย่างนี้คลาส SavingAccount และ CurrentAccount เป็น sub class ของ BankAccount นั่นเอง

    Generalization

    ✨ ของที่อยู่ใน Base class จะถูกส่งลงมาให้ Sub class ส่วน sub class จะใช้มันได้หรือเปล่าขึ้นอยู่กับ accessibility ของ property พวกนั้น

    เราจะเห็นว่าแม้ SavingAccount จะไม่ได้เขียนโค้ดอะไรไว้ข้างในเลย แต่เราก็จะสามารถเรียกใช้ Balance หรือเมธอต Deposit ได้ ตามโค้ดด้านล่าง

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

    Override (การเปลี่ยนพฤติกรรม)

    อีกสิ่งหนึ่งที่เราได้จากการทำ Inheritance นั่นคือ เราสามารถทำให้การทำงานของ Sub class ทำงานแตกต่างกันกับ Base class ของมันได้ เช่น ตัวชัญชีนั้นจะต้องสามารถ ถอนเงิน ได้ ซึ่งจุดที่น่าสนใจของมันคือ

    • บัญชีออมทรัพย์ - ถอนเงินได้สูงสุดไม่เกินเงินที่มีอยู่ในบัญชี

    • บัญชีกระแสรายวัน - ถอนเงินได้เกินเงินที่มีอยู่ในบัญชี แต่ไม่เกินวงเงินที่ตั้งไว้

    ดังนั้นเราก็จะทำการเขียนโค้ดถอนเงินไว้ที่ตัว BankAccount ที่เป็น Base class ของมันก่อน ซึ่งได้ประมาณนี้

    คำสั่ง virtual เป็นคำสั่งพิเศษของภาษา C# เพื่อที่จะให้ Sub class สามารถไปแก้ไขการทำงานของเมธอตนั้นๆได้ และผมได้แก้ให้ตัวแปร Balance สามารถเข้าไปแก้ไขผ่าน Sub class ได้เท่านั้น

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

    ลองทดสอบกันดู

    Output (Saving) Saladpuk, has THB 500. (Current) Saladpuk, has THB -300.

    เรียบร้อยแล้ว บัญชีออมทรัพย์ ถอนเงินได้ไม่เกินเงินที่มีอยู่ในบัญชี แต่ในขณะที่ บัญชีกระแสรายวันถอนเกินเงินที่มีอยู่ได้นั่นเอง

    🤔 ทำ Inheritance ไปทำไม ?

    กลับมาที่บัญชีธนาคารเหมือนเดิม ซึ่งนอกจากการฝากเงินแล้ว เรายังต้องทำให้มัน ปิดบัญชี ได้ด้วย ซึ่งถ้าเราใช้ความสามารถของ Inheritance เราเลยสามารถไปเขียนเมธอตในการปิดบัญชีไว้ที่ Base class เพียงที่เดียว แล้วเจ้า Sub class ของมันก็จะได้รับความสามารถนี้ไปด้วยทันทีนั่นเอง (ขอเขียนแบบย่อๆนะ)

    ความสัมพันธ์แบบ Is a

    เมื่อไหร่ก็ตามที่เราใช้ Inheritance ปุ๊ป เจ้าพวก Sub class ทั้งหลายจะมีความสัมพันธ์ที่เรียกว่า "Is a" ทันที หมายความว่า เราก็จะมองว่า บัญชีออมทรัพย์ และ บัญชีกระแสรายวัน มันเป็น บัญชีธนาคาร ประเภทหนึ่งทันที ซึ่งมันจะมีบทบาทที่สำคัญในเรื่องของการนำไปใช้กับ Polymorphism ในบทถัดไปอย่างมาก

    ข้อควรระวัง อย่าใช้ Inheritance เพราะเราขี้เกียจเขียนโค้ดซ้ำๆ เพราะมันจะได้ความสัมพันธ์แบบ Is A เข้าไปด้วย (เดี๋ยวไปดูความร้ายแรงของมันในบทของ Polymorphism เอาละกัน)

    จงทำ Inheritance เมื่อ Class พวกนั้นมันเป็นประเภทเดียวกันจริงๆ จากมุมมองของ Abstraction เท่านั้น

    ลำดับชั้น

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

    โดยเราจะเห็นว่า BankAccount เป็นคลาสแม่ ซึ่งมีลูก 2 ตัวคือ SavingAccount และ CurrentAccount

    แต่มันยังไม่จบเพียงเท่านี้เพราะทุกคลาสจริงๆมันจะสืบสอดมาจากคลาสที่ชื่อว่า Object อีกที ดังนั้นแผนภาพที่แท้จริงคือ

    ซึ่งจากรูปนี้คลาส BankAccount ก็เป็นคลาสลูกของคลาส Object อีกต่อหนึ่งนั่งเอง เลยทำให้มันมีความสามารถต่างๆของคลาสแม่ติดมาด้วยเสมอยังไงล่ะ เช่น เราใช้คำสั่ง .ToString() ได้เลยนั่นเอง

    Output demo.BankAccount

    หมายเหตุ: โดยปรกติ .ToString() ที่เขียนไว้ใน Base Class จะเป็นการแสดงชื่อเต็มๆโดยขึ้นต้นจาก Name space แล้วต่อด้วยชื่อคลาสของเรานั่นเอง

    หมายเหตุ ใน C# เราสามารถทำการสืบทอดได้ทีละ 1 คลาสเท่านั้น หรือพูดง่ายๆคือ มีแม่ได้เพียงคนเดียว นะจ๊ะ แต่ในหลักการเรื่องนี้มันมีอีกหลายแบบเลย เช่น มีแม่ได้มากกว่า 1 ตัว (Multiple Inheritance) แบบลูกครึ่ง (Hybrid inheritance) ซึ่งเรื่องเหล่านั้นลองไปหาอ่านดูเอาเองต่อนะจ๊ะ

    แนะนำให้อ่าน ใครที่สนใจอยากรู้การแปลงโค้ดให้เป็นภาพที่เข้าใจได้ง่ายๆ สามารถศึกษาเพิ่มเติมได้ที่บทความตัวนี้เลย 👶 UML พื้นฐาน

    🎥 วีดีโอประกอบความเข้าใจ

    ทฤษฎี

    ตัวอย่างการใช้งาน

    ตัวอย่างโค้ดทั้งหมด

    object
    class
    prototype-based inheritance
    class-based inheritance
    sub classes
    base class
    constructors
    overloaded operators
    friend functions
    [1]
    realizing an interface
    reuse code
    directed graph
    Simula
    [2]
    แก้ไขเปลี่ยนแปลง member อื่นๆได้ เราก็สามารถใส่
    readonly
    เข้าไปได้ตามโค้ดด้านล่างครับซึ่งมันจะบังคับให้เราแก้ไข member อื่นไม่ได้เลย เพราะมันจะ compile ไม่ผ่านนั่นเอง

    🔥 Default interface members

    โดยปรกติ Interface นั้นเราจะไม่สามารถมี member ที่มี implementation ภายในได้ แต่ด้วยความสามารถใหม่นี้จะทำให้เราสร้าง member ที่มี implementation ภายใน interface ได้แล้ว โดยโค้ดด้านล่างเป็นการเขียน interface แบบเดิม

    จากโค้ดด้านบนเราจะเห็นว่ามันไม่สามารถมี implementation ภายใน method Add ได้ ส่วนโค้ดด้านล่างจะเห็นว่าเราสามารถใส่ implementation เข้าไปได้แล้ว

    ความต่างของ interface กับ abstract กับจะเหลือเรื่อง multi inheritance เท่านั้นละ

    🔥 More patterns in more places

    ต่อยอดความสามารถของ C# version 7 เรื่องการทำ pattern matching ของคำสั่ง switch โดยมันมีความสามารถเพิ่มเข้ามาตามนี้เลย

    💡 Switch expressions

    เราสามารถย่อการทำงานของคำสั่ง switchให้มันสั้นลงได้แล้ว ยกตัวอย่างเช่นเราต้องทำงานกับ enum ที่มีค่าตามโค้ดล่างนี้

    ถ้าเป็นโค้ดแบบเดิมเราจะต้องเขียนการทำงานภายใน method เป็นแบบนี้

    แต่ด้วยความสามารถใหม่เราจะสามารถย่อมันเข้ามาเป็นแบบนี้ได้

    💡 Property patterns

    ถัดมาเราสามารถ map ค่าของ property ของ object ภายในคำสั่ง switch ได้เลย เช่นเรามีคลาส Address ที่เก็บรหัสจังหวัดไว้ใน property ที่ชื่อว่า State ดังนั้นเราก็สามารถเอา object ของ address มาใช้กับคำสั่ง switch ได้ตามด้านล่างเลย

    💡 Tuple patterns

    ความสามารถนี้ต่อยอดจากความสามารถของ C# version 7 เรื่อง tuple ซึ่งมันจะทำให้เราสามารถ map tuple เข้ากับคำสั่ง switch ได้แล้ว เช่นผมจะเขียนโค้ดในการตรวจว่าเกมเป่ายิงฉุบถ้าผลออกมาแบบนี้แล้วผลลัพท์จะเป็นแพ้ชนะหรือเสมอ โดยการส่ง string เข้าไป 2 ตัว เราก็สามารถใช้ความสามารถใหม่แบบเขียนเป็นแบบนี้ได้เลย

    💡 Positional patterns

    จากความสามารถเดิมของ C# version 7 เราสามารถทำการจับคู่ระหว่างตำแหน่งของ deconstructor ของ object ได้ เช่นจากเดิมเวลาที่เราจับคู่เราจะต้องจับแบบนี้

    แต่ด้วยความสามารถใหม่เราจะสามารถจับคู่แบบนี้ได้

    💡 Recursive patterns

    จากที่ว่ามาทั้งหมด เราสามารถเขียน pattern ที่ซ้อน pattern เข้าไปได้ด้วย ตามนี้เลย

    🔥 using declarations

    แต่เดิมเราสามารถใช้คำสั่ง using เพื่อสร้างตัวแปรที่มันจะคืนทรัพยากรแบบอัตโนมัติเมื่อจบ block ของมันได้แล้ว ตามโค้ดด้านล่างเราจะเห็นว่า using แบบเดิมเมื่อ block ของมันจบลง ตัวแปรนั้นก็ทำการคืนทรัพยากรหลังจาก block ทันที

    ด้วย C# version 8 เราสามารถสร้างตัวแปรที่มันอยู่ได้ตลอดภายใน block ที่มันอยู่ และจะคืนทรัพยากรเมื่อมันจบ block ของมันแล้วได้ ตามตัวอย่างด้านล่าง

    🔥 Static local functions

    ความสามารถใหม่ตัวนี้ต่อยอดจาด C# version 7 เราจะสามารถสร้าง local function ได้ แตมาใน version 8 นี้เราจะสามารถสร้าง local function ที่เป็น static ได้ โดยความสามารถใหม่นี้จะป้องกันไม่ให้เราเผลอไปเรียกใช้งานตัวแปรที่เป็นพวก local variable นั่นเอง ลองดูโค้ดด้านล่างนี้ที่เขียนด้วย local function ของ C# version 7 ซึ่งเราอาจจะเผลอไปเรียกใช้งาน local variable ได้

    โค้ดด้านล่างคือความสามารถใหม่ที่เป็น static local function ซึ่งมันจะป้องกันไม่ให้เราไปเรียกใช้ local variable ได้เลย ซึ่งถ้าเผลอไปเรียกมันจะ compile ไม่ผ่านทันที

    🔥 Disposable ref structs

    ใน C# version 7 เขาได้เพิ่มความสามารถใหม่เข้ามานั่นคือคำสั่ง ref ซึ่งจะทำให้เราสามารถอ้างอิงของในรูปแบบ reference type ได้ ซึ่งจากไอเดียนี้เราก็จะสามารถสร้าง struct ที่เป็น ref ได้เช่นกันตามโค้ดด้านล่าง

    แต่ด้วยข้อจำกัดบางประการเลยทำให้เจ้า struct ไม่สามารถ implement interface ได้เลย ดังนั้นเวลาที่เราสร้าง struct ที่จำเป็นต้องคืนทรัพยากรหลังใช้งานเสร็จก็จะมีปัญหา เพราะเราจะไป implement IDisposable ไม่ได้นั่นเอง ทำให้มีโอกาสสูงที่จะลืมคืนทรัพยากร และ แน่นอนว่าคำสั่ง using ก็ไม่สามารถใช้ได้ด้วยนั่นเอง

    จากที่เกริ่นมาทั้งหมดนั้นเจ้า C# version 8 ได้แก้ปัญหาให้ struct มีความสามารถใช้ Dispose ได้แล้วโดยที่ไม่ต้อง implement IDisposable เลย โดยแค่สร้าง method Dispose ให้เป็น public เพียงเท่านี้เอง ตามโค้ดด้านล่าง

    ส่วนใครที่ต้องการเรียกใช้ struct ตัวนี้ก็สามารถใช้คำสั่ง using ลงไปดื้อๆได้เลยตามโค้ดด้านล่างนี้

    🔥 Nullable reference types

    เวลาที่เราทำงานกับ reference type นั้นมีบ่อยครั้งที่เราอาจเจอ error ประเภท NullReferenceException เด้งมาให้เห็นบ่อยๆ ดังนั้นใน C# version 8 นี้เขาได้เพิ่มการแจ้งเตือนแบบใหม่ขึ้นมา เพื่อแยกของต่างๆออกจากกันว่าข้อมูลตัวนี้เป็น null ได้ ตัวนี้ไม่มีทางเป็น null ซึ่งการแจ้งเตือนทั้งหมดจะแสดงผลผ่าน warning ออกมานั่นเอง ส่วนถ้าอยากเปิดการใช้การแจ้งเตือนแบบใหม่ เราจะต้องใส่แทก #nullable enable ตัวนี้เข้าไป เพื่อบอกว่าเราจะเปิดใช้งานการตรวจสอบ null แล้วนะตามโค้ดด้านล่าง

    ส่วนในการใช้งานเราจะต้องทำการระบุว่าตัวแปร reference type ไหนบ้างที่เป็น null ได้ โดยตัวแปรพวกนั้นก็จะใช้คำสั่ง ? ด้านหลัง data type เพื่อเป็นการประกาศว่ามันเป็น nullable นั่นเอง ตามโค้ดด้านล่างเลย

    🔥 Asynchronous streams

    จาก C# version 5 นั่นเราได้รู้จักกับคำสั่ง async กันไปแล้ว แต่ถ้าเราใช้งานเราจะพบว่ามันมีปัญหาในการทำงานเวลาที่เราจะค่อยๆส่งข้อมูลออกมาเรื่อยๆ ในลักษณะของ streaming ดังนั้นใน C# version 8 นี้เขาก็ได้แก้ปัญหานี้ให้เราแล้วโดยใช้คำสั่ง IAsyncEnumerable<T> นั่นเอง จากโค้ดด้านล่างผมจะส่งเลข 1-20 มาเป็น streaming ผมก็จะเขียนได้ว่า

    ส่วนคนที่เรียกใช้ method นี้ก็สามารถใช้คำสั่ง await เพื่อทำงานร่วมกับคำสั่ง foreach ได้เลยด้วย ตามโค้ดด้านล่าง

    🔥 Indices and ranges

    ความสามารถใหม่ในการเข้าถึงข้อมูลที่เป็นตระกูล Array collection

    💡 Indices

    สมัยก่อนถ้าเราจะเข้าไปทำอะไรซักอย่างกับ Array แล้วล่ะก็ เราจะต้องเข้าถึงผ่านเจ้าตัวที่เรียกว่า indexer เช่น ขอเข้าถึงข้อมูลตัวแรก [0] หรือข้อมูลตัวที่ 3 [2] ประมาณนี้ แต่ด้วยความสามารถใหม่นี้เราจะมีตัวอ้างอิง Array collection แบบกลับหลังได้ด้วย ตามโค้ดด้านล่างนี้เลย

    จากโค้ดด้านบนจะเห็นว่าเราสามารถอ้างอิงข้อมูล Array collection แบบกลับหลังได้โดยใช้เครื่องหมาย ^ นั่นเอง เช่นผมอยากได้ข้อมูลตัวสุดท้ายก็จะเขียนออกมาแบบนี้ได้เลย

    💡 Ranges

    นอกจากการเข้าถึง Array collection แบบใหม่แล้ว เขายังรองรับการทำงานกับ Array collection แบบเข้าถึงข้อมูลเป็นช่วงได้ด้วย เช่นผมอยากได้ข้อมูลตัวที่ 2 ถึงตัวที่ 4 นั่นก็คือ quick brown fox ผมก็สามารถเขียนโค้ดง่ายๆตามด้านล่างนี้เลย

    จากโค้ดด้านบนเราจะมีคำสั่งใหม่นั่นคือคำสั่ง .. ซึ่งจะเป็นตัวบอกว่าจะเอาข้อมูลตั้งแต่ตัวไหนและสิ้นสุดที่ตัวไหนนั่นเอง และแน่นอนมันก็ใช้กับการอ้างอิงแบบกลับด้านได้เช่นกัน เช่นผมอยากได้ข้อมูล 2 ตัวสุดท้าย lazy dog ผมก็สามารถเขียนแบบนี้ได้เช่นกัน

    และที่เจ๋งไปกว่านั้นของคำสั่ง .. ก็คือถ้าเราไม่ระบุว่าจะเอาข้อมูลจากไหนถึงไหน มันก็จะกวาดมาทั้งหมดเลย เหมือนกับคำสั่ง * ของพวก SQL นั่นเอง

    ยังไม่หมดเพียงเท่านี้ เรายังสามารถสร้างตัวแปรแบบ Range เก็บเอาไว้ใช้งานได้ด้วยนะ

    🔥 Null-coalescing assignment

    เวลาที่เราทำงานกับ reference type หรือ Nullable หรือพูดง่ายๆคือข้อมูลที่มันอาจจะเป็น null ได้ เราจะต้องคอยระวังเวลาใช้งานมันเสมอเพราะไม่งั้นจะได้ NullReferenceException โผล่มาจ๊ะเอ๋ได้ ดังนั้นเราอาจจะต้องตรวจค่ามันว่าเป็น null หรือเปล่าเพื่อป้องกันมันเป็น null ก่อนเรียกใช้งานราวๆนี้

    แต่ด้วยความสามารถใหม่ของ C# version 8 เราสามารถใช้คำสั่ง ??= เพื่อเป็นการบอกว่า ถ้าค่าด้านซ้ายเป็น null ให้เอาค่าด้านขวาไปกำหนดให้ค่าด้านซ้ายได้เลย แต่ถ้าไม่เป็น null ให้ข้ามไป ดังนั้นโค้ดใหม่เราก็จะออกมาเหลือเพียงแค่นี้

    🔥 Unmanaged constructed types

    ตั้งแต่ C# version 7.3 ลงมานั้นตัว constructed type จะไม่สามารถเป็น unmanaged type ได้ แต่ในตัว C# version 8 นั้นถ้า construct value นั้นมีแต่ members ที่เป็น unmanaged types แล้วล่ะก็ตัวมันก็จะเป็น unmanaged เช่นกัน เช่นผมมี constructed type ตัวนึงตามโค้ดด้านล่างนี้

    แล้วผมไปทำการสร้าง Coords โดยส่ง T เป็น unmanaged types เช่น Coords<int> Coords<bool> บลาๆ ซึ่งภายในของ Coords นั้นมีแต่ unmanged types อย่างเดียว ผมก็จะสามารถใช้ pointer หรือ stackalloc ไปเล่นกับมันได้นั่นเอง

    ปรกติผมไม่ได้เล่นกับ pointer มานักถ้าสนใจก็สามารถไปศึกษาเพิ่มเติมได้จากลิงค์ด้านล่างนี้ครับ

    • Microsoft Document - Stackalloc

    • Microsoft Document - Unmanaged types

    🔥 Enhancement of interpolated verbatim strings

    หลังจากที่ C# version 7 ได้นำเสนอความสามารถในการสร้างตัวอักษรแบบใหม่ด้วยเครื่องหมาย $ กันแล้ว ถ้าเพื่อนๆหลายๆคนได้ลองใช้คู่กับเจ้าเครื่องหมาย @ นี้แล้วละก็จะพบว่า มันทำงานร่วมกันไม่ได้ แต่ตอนนี้ C# version 8 ก็ได้ออกมาแก้ไขให้มันสามารถใช้งานร่วมกันได้แล้ว เย่ๆ โดยจะเอาอันไหนขึ้นก่อนขึ้นหลังก็ได้ครับ ตามตัวอย่างด้านล่างเลย

    🎥 วีดีโอประกอบความเข้าใจ

    Single-Responsibility Principle
    Open & Close Principle
    Dependency-Inversion Principle
    SimpleInjector
    SendGrid
    Proxy
    Single-Responsibility Principle
    Open & Close Principle
    Dependency-Inversion Principle
    Mr.Saladpuk
    Object Adapter Pattern (Wikipedia)
    Class adapter Pattern (Wikipedia)
    ส่งข้อมูลไปกลับได้ทั้ง 2 ฝั่ง
    ช่องทางสนับสนุนค่าอาหารแมวน้ำกั๊ฟ 😘
    Visitor Pattern
    🤴 Design Patterns

    Abstract Factory

    แนวคิดในการสร้างกลุ่มของ object ที่มีความสัมพันธ์กัน

    เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า โครงร่างของโรงงานผลิต และมันอยู่ในกลุ่มของ 🤰 Creational Patterns ซึ่งเจ้าตัวนี้จะมาช่วยแก้ปัญหาเมื่อเราต้องการจะสร้าง object ที่มีความสัมพันธ์กัน ซึ่งอ่านแล้วอาจจะ งงๆ หน่อย ดังนั้นลองไปดูโจทย์ของเรากันเลยละกัน

    💡 ถ้าอยากเข้าใจ Abstract Factory Pattern ตัวนี้ได้เร็วขึ้น แนะนำให้อ่าน 🏭 Factory Method Pattern ก่อนนะครัช (เพราะมันแทบจะเหมือนกันเลย)

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช 👦 Design Patterns

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

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    🧐 โจทย์

    สมมุติว่าเราเขียนเกมที่มีแผนที่ 2 แบบคือแบบ ป่า (Payon) และ ทะเลทราย (Desert) ตามรูป

    ซึ่งภายในแต่ละแผนที่จะมี monster หลายๆแบบอยู่ในนั้น เช่น สไลม์ (Slime), หมาป่า (Wolf), นกยักษ์ (Giant Bird) แต่เนื่องจากสภาพแวดล้อมต่างกันเลยทำให้ monster ที่อยู่ในนั้นมี หน้าตา กับ ชื่อเรียก ไม่เหมือนกัน ตามรูปด้านล่าง

    แล้วเราจะเขียนโค้ดกันยังไงดีล่ะ เพื่อให้แผนที่ทั้ง 2 แบบ สามารถสร้าง monster พวกนี้ออกมาได้ถูกตามเงื่อนไข และ โค้ดจะต้องมีความยืดหยุ่นสูงด้วยนะ ?

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

    จากตรงนี้ผมอาจจะมองว่ามี monster อยู่ทั้งหมด 3 ประเภท ดังนั้นผมก็จะแบ่งมันออกเป็น Model ทั้งหมด 3 กลุ่มตามรูปด้านล่างเบย

    ส่วนตอนเขียนโค้ดเราก็แค่ สร้างเมธอดแยกตามประเภท monster และก็ไปเขียนเงื่อนไขเอาว่า ตอนนี้อยู่แผนที่อะไร เพียงเท่านี้เราก็จะสามารถสร้าง monster ออกมาได้ตรงตามเงื่อนไขที่ว่ามาละ ตามโค้ดด้านล่างเบย

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

    Open & Close Principle (OCP) อันนี้เป็นตัวอย่างในการออกแบบที่ละเมิดหลักในการออกแบบที่ชื่อว่า OCP นั่นเอง ซึ่งมันทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สังเกตุได้ว่าถ้ามี แผนที่แบบใหม่เข้ามา เราก็จะต้องไปไล่แก้เจ้าพวก IF-ELSE ที่อยู่ด้านบนกันใหม่ทุกครั้งนั่นเอง

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

    เจ้าโค้ดทั้งหมดที่ทำไว้ด้านบนจริงๆมันก็ เกือบจะเป็น Abstract Factory แล้วล่ะ แต่มันยังขาดหลักในการออกแบบอีกหลายอย่าง เลยทำให้ในอนาคตเราจะทำงานด้วยยาก ซึ่งก่อนที่เราจะไปกันต่อ ผมขอแปลงโค้ดทั้งหมดของเราให้กลายเป็นภาพที่ดูง่ายๆ ตามรูปด้านล่างนี้ละกันนะ

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

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

    จากปัญหาที่ว่ามาเราจะพบว่า ทุกครั้งที่มีแผนที่ใหม่ หรือ monster แบบใหม่ๆเข้ามา มันจะทำให้ เราต้องไปแก้เจ้าคลาส MonsterFactory เสมอ เลยทำให้ในอนาคตมันจะ บวมฉ่ำ อย่างไม่ต้องสงสัยเลย

    ส่วนสาเหตุการบวมนั้นเกิดจากเจ้า MonsterFactory ของเรามันดันไปดูแลทุกอย่างเลยยังไงล่ะ เช่น ดูแลเรื่องแผนที่ ดูแลเรื่องการสร้าง monster ซึ่งนี่คือหนึ่งในการละเมิดกฏของ SRP นั่นเอง

    Single-Responsibility Principle (SRP) เจ้าสิ่งนั้นๆควรมีหน้าที่รับผิดชอบเพียงอย่างเดียว เพราะถ้ามันดูแลหลายอย่าง นั่นหมายความว่า เวลาที่ Requirement เปลี่ยนมาทีนึง มันก็จมีโอกาสสูงมากที่การเปลี่ยนนั้นมันจะไปกระทบเจ้าสิ่งนั้น ทำให้เราต้องแก้ไขมัน ซึ่งผองเพื่อนอื่นๆที่มันดูแลอยู่นั้นไม่ได้เกี่ยวข้องเลยก็มีผลกระทบด้วยนั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย

    ดังนั้นเราจัดการเรื่อง SRP เสียก่อน โดยทำการแยกของที่อยู่ในนั้นออกมาเป็นเรื่องๆ ซึ่งในตัวอย่างของเราก็มีแค่ 2 เรื่องนั่นคือ แผนที่ กับ Monster นั่นเอง

    และถ้าเราดูความสัมพันธ์ของเจ้า 2 อย่างนี้ดีๆเราจะพบว่า แผนที่เป็นตัวกำหนดว่าจะสร้าง Monster แบบไหน นั่นเอง

    แล้วถ้าเราดูเจ้าแผนที่ทั้ง 2 เราก็จะพบว่ามัน สร้างของประเภทเดียวกัน แต่ต่างกันที่รายละเอียด นั่นเอง เช่น แผนที่ต้องการสร้าง สไลม์, หมาป่า และ นกยักษ์ เหมือนกัน แต่ผลลัพท์จริงๆนั้นจะขึ้นอยู่กับแผนที่นั่นเอง ตามรูปด้านล่าง

    🔥 แก้ไขปัญหา

    จากที่ร่ายยาวมเราจะเริ่มมองเห็น รูปแบบ + หน้าที่รับผิดชอบ ของต่างๆตามนี้

    • แผนที่ มีหน้าที่รับผิดชอบ สร้าง monster ต่างๆ

    • แผนที่ มีรูปแบบในการสร้าง monster เหมือนๆกัน (สไลม์, หมาป่า, นกยักษ์)

    • Monster ต่างๆ เป็นแค่ ผลลัพท์ ที่เราจะเอาไปใช้ต่อเท่านั้น

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

    ซึ่งเจ้าภาพด้านบน มันเป็นการสร้างกลุ่มของ monster ที่เป็นประเภทเดียวกันนั่นคือ

    • PayonMonsterFactory จะสร้าง monster ที่อยู่ในป่า

    • DesertMonsterFactory จะสร้าง monster ที่อยู่ในทะเลทราย

    ดังนั้นถ้าเรามองจาก พฤติกรรม ของคลาสทั้ง 2 เราก็จะสามารถแยกมันออกมาเป็น Interface ที่ใช้ในการสร้าง Monster ได้ตามรูปด้านล่างนั่นเอง

    หรือถ้าจะเขียนเป็นภาพแบบเต็มๆที่ถูกต้องก็จะได้รูปแบบนี้

    เพียงเท่านี้เราก็สามารถแก้ปัญหาโจทย์นี้ได้เรียบร้อยแล้ว แถมเมื่อเราทำงานกับโรงงานพวกนี้ เรายังสามารถใช้ Best Practice ในเรื่องของ ได้อีกด้วย เพราะเราไม่ได้ทำงานกับระดับ Implementation แล้วยังไงล่ะ ซึ่งมันก็เป็นการเข้าข่ายกับ DIP ไปด้วยในตัวนั่นเอง เย่ๆ

    Dependency-Inversion Principle (DIP) เป็นหนึ่งในหัวใจของการออกแบบให้เราไม่ไปผูกการทำงานไว้กับ Low level module นั่นเอง ส่วนใครที่อยากศึกษาเรื่องนี้เพิ่มเติมก็สามารถกดไปอ่านได้จากลิงค์นี้เบย

    ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า Abstract Factory Pattern เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏

    🤔 Abstract Factory คือไย ?

    เจ้าตัวนี้มันจะคล้ายกับ มาก ดังนั้นผมจะเขียนจุดที่มันต่างกันของ Abstract Factory ไว้ใน Quote นะครับ

    🔥 จุดกำเหนิด

    ในบางทีการสร้าง object นั้นมันก็ไม่ได้ง่ายเลย เช่น constructor มันรับหลายๆ parameters ก็ปวดหัวละ หรือถ้ามีการสร้าง object ที่สร้างยากๆตัวเดียวกันหลายๆจุดขึ้นมา มันก็แสดงว่าเราก็จะมีโค้ดแบบเดียวกันอยู่ซ้ำๆหลายที่เต็มไปหมด และการที่เราไปสร้าง object เองในบางทีก็อาจทำให้ + ที่วางไว้เสียหายโดยไม่ได้ตั้งใจก็เป็นได้

    จุดที่เป็นเรื่องเฉพาะของเรื่องนี้ แถมถ้าเราต้องสร้าง object ที่มันต้องไปด้วยกันเป็นเซตเราจะรู้ได้ยังไงว่าเราสร้างมันได้ถูก ไม่ได้เอาเซตอื่นๆมาปนกันมั่ว?

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

    สามารถสร้าง object ที่พร้อมสำหรับใช้งานได้ โดยที่เราไม่ต้องระบุ data type ที่แท้จริงของ object ที่เราจะสร้างเลย

    จุดที่เป็นเรื่องเฉพาะของเรื่องนี้ object ที่เราสร้างมาจาก Factory เดียวกันมันจะเป็นของเซตเดียวกัน

    🔥 วิธีการใช้

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

    object อะไรก็ตามที่เราอยากจะสร้าง เราจะเรียกมันว่า Product ซึ่งโดยปรกติเราก็จะมี product หลายๆแบบ เลยต้องทำมันเป็น Interface เอาไว้ (ตรงจุดนี้จริงๆจะเป็นอะไรก็ได้นะ ขอแค่เป็น abstraction ก็พอ)

    ส่วนคลาส Product ที่แท้จริงนั้นก็จะไป implement IProduct อีกที ซึ่งโดยปรกติเราจะเรียกคลาสที่แท้จริงเหล่านั้นว่า Concrete class นั่นเอง ดังนั้นในกรณีนี้ผมจะเรียกมันว่า ConcreateProduct ละกัน

    และเวลาใช้งานจริงๆ Product ประเภทเดียวกันก็อาจจะมีหลายแบบก็ได้ เช่น Slime ยังมี Drops กับ Poporing ไรงี้ ดังนั้นก็จะได้ภาพออกมาราวๆนี้

    คราวนี้ถ้าเรามี Product หลายๆประเภท และแต่ละประเภทก็มีหลายๆแบบด้วยนะ เราก็จะได้ภาพออกมาราวนี้ๆ

    และในบางที Product ที่อยู่ต่างประเภทกัน ก็อาจจะถูกจัดไว้ในเซตเดียวกันก็ได้ เช่น เซตธาตุไฟ เราก็จมี Slime ธาตุไฟ กับ หมาป่าธาตุไฟไรงี้

    ส่วนตัวที่ทำหน้าที่สร้าง object เราจะเรียกมันว่า Factory ซึ่งการสร้างในรอบนี้มันมีเรื่องเซตมาเกี่ยวข้องด้วย ดังนั้นการสร้าง product มันจะต้องสามารถสร้างของทุกประเภทได้ทั้งหมดเลย โดยที่ product ที่ได้มามันจะต้องอยู่ภายในเซตเดียวกันนั่นเอง

    แต่ตัว Factory เองนั้นมันไม่รู้หรอกว่ามันจะต้องสร้างอะไรออกมา ดังนั้นมันเลยต้องปล่อยให้เป็นหน้าที่ของคลาสระดับล่าง ที่รู้ว่ามันจะสร้าง product อะไรมาให้ ดังนั้นเลยทำให้เจ้า Factory ของเราคงอยู่แค่ในสภาพ Interface ก็เพียงพอแล้วนั่นเอง

    นี่แหละคือที่มาของคำว่า Abstract Factory เพราะตัวมันเองคือ โครงร่างของโรงงานผลิต เพียงเท่านั้น

    ดังนั้นคลาสระดับล่างที่รู้ว่าจะต้องสร้างอะไร ก็รับผิดชอบ Implement เจ้า Abstract Factory นี้ไปซะ

    และเมื่อมันรู้ว่าจะต้องสร้าง product อะไรออกมา มันจะต้อง สร้าง product ที่อยู่ในเซตเดียวกัน ออกมาด้วยนะ

    ซึ่งทั้งหมดที่ร่ายยาวมานั่นก็คือแนวทางในการออกแบบที่ชื่อว่า Abstract Factory Pattern นั่นเอง ซึ่งหน้าตาของมันก็จะประมาณรูปด้านบนนั่นแหละ แต่ขอเติมสัญลักษณ์ให้เข้าใจง่ายๆหน่อยนึงตามรูปด้านล่างละกันนะ

    ไหนลองเอาที่เราออกแบบมาเทียบกันดูดิ๊ ... เหมือนกันเปี๊ยบเบย

    ข้อแนะนำ ของทุกอย่างใน Design Patterns ทุกตัว เราไม่จำเป็นต้องทำตาม หรือ มีครบเหมือนตามที่เขาบอกไว้ก็ได้ (ถ้าเข้าใจ + มีเหตุผลที่ดีพอ) เพราะสิ่งที่ Pattern แต่ละตัวต้องการจะบอกเราคือ แนวทาง และเหตุผลในการออกแบบเพียงเท่านั้น ซึ่งสิ่งที่เราต้องทำต่อก็คือนำมันไปประยุกต์ให้เข้ากับปัญหาที่เราเจออยู่ให้เหมาะสมนั่นเอง

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

    ข้อดีที่สุดของการนำ Abstract Factory Pattern มาใช้ก็คือ มันลดการผูกกันของโค้ด (decoupling) นั่นเอง เพราะถ้าเราเขียนสร้าง object โดยใช้คำสั่ง new แบบเดิม มันจะมีปัญหาหลายๆอย่างตามมา เช่น

    ตัวอย่างพร้อมเหตุผลว่าทำไมถึงควรใช้ และมันมาแก้ปัญหาเรื่องอะไรบ้างของ Abstract Factory Pattern นั้นส่วนใหญ่มันจะเหมือนกับ Factory Method Pattern ซึ่งผมเขียนไว้ในบทความของ factory method แล้วยาวม๊วก เลยไม่อยากจะ copy มาใส่ตรงนี้ ดังนั้นรบกวนเพื่อนๆกดลิงค์ด้านล่างไปอ่านเอาละกันนะ แล้วจะเห็นภาพขึ้นเยอะเลยว่าทำไมถึงควรจะใช้มัน

    😱 สร้าง object ผิดเซต

    ถ้าเราต้องมาสร้าง object ด้วยตัวเองทุกครั้ง เราจะมั่นใจได้ยังไงว่าเราไป new object ในเซตที่เรากำลังทำงานด้วยอยู่ เช่น เราจะทำการสร้าง object ของ หมา, แมว, หมู, เสือ ก็จะเขียนออกมาได้ราวๆนี้

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

    🤔 ใช้แล้วดียังไง ?

    อย่างที่บอกไปว่าเจ้าตัวนี้มันคล้ายกับ Factory Method Pattern ดังนั้นส่วนใหญ่ไปดูได้จากตัวนั้นเลย ดังนั้นเราจะมาตอบข้อดีเฉพาะของ Abstract Factory กันดีกว่า

    👨‍🔧 สร้าง object ผิดเซต

    เพียงแค่เราเปลี่ยนมาใช้ pattern นี้ มันก็เป็นการบังคับเราไปในตัวอยู่แล้วว่าให้เราสร้าง object ผ่านพวก ConcreteFactory ดังนั้นโค้ดเรา เมื่อจะสร้าง หมา แมว หมู เสือ ก็จะกลายเป็นแบบนี้

    ซึ่งถ้ามันยังสร้างผิดอีก นั่นแสดงว่าตัวที่ implement interface นั้นไปสร้าง object ผิดตัว ดังนั้นถ้าเราแก้มันเสร็จปุ๊ป ทุกจุดที่เรียกใช้มันก็จะไม่มี bug ตัวนี้อีกแล้วนั่นเอง (ดีกว่าไปไล่เช็คทุกจุดยังไงล่ะ)

    ⚔️ Abstract Factory vs Factory Method

    ผมเชื่อว่าถ้าใครได้ดู Factory Method Pattern กับ Abstract Factory Pattern แล้ว ก็อาจจะ งงๆ กันอยู่นะว่ามันเหมือนหรือต่างอะไรกันบ้าง ดังนั้นตรงนี้จะมาไขข้อข้องใจกันครัช

    • วัตถุประสงค์ของทั้ง 2 ตัวนั้น เหมือนกันคือ ช่วยสร้าง object ที่จะเกิดขึ้นจากการใช้คำสั่ง new ตรงๆ

    • Factory Method Pattern จะแก้ปัญหาผ่าน Inheritance โดยให้ Sub class เป็นคนจัดการ

    • Abstract Factory Pattern จะแก้ปัญหาผ่าน Composition โดยใช้คลาสนั้นๆไป implement เอาเอง

    เกร็ดความรู้ Abstract Factory Pattern นั้นภายในการทำงานจริงๆ ส่วนใหญ่ก็จะไปเรียกใช้งาน Factory Method Pattern มาทำงานต่ออีกทีนึงเช่นกัน เพราะมันก็จะช่วยลด Coupling ลง และยังช่วยทำให้โปรเจคของเราเปิดรับของต่างๆมากยิ่งขึ้นนั่นเอง

    🎯 บทสรุป

    👍 ข้อดี

    การนำ Abstract Factory Pattern มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง ทำให้เราสามารถเปลี่ยนแปลง แก้ไข รองรับสิ่งต่างๆได้มากขึ้น และมันยังช่วยเปิดให้เราทำพวก Inversion of Control (IoC) ได้ง่ายขึ้นด้วย

    👎 ข้อเสีย

    • แค่จะสร้าง object ใหม่เฉยๆ ก็เพิ่มโค้ดเข้าไปมหาศาลแล้ว ดังนั้นโครงสร้างจะซับซ้อนขึ้นอีกเยอะเลย ดังนั้นก่อนใช้ให้คิดให้ดีก่อนว่า เรามีปัญหาถึงขนาดที่ต้องใช้มันหรือเปล่า?

    • ถ้ามี product แบบใหม่ๆเข้ามา มันจะทำให้เราต้องแก้ interface ตามด้วย ดังนั้นมันจะส่งผลกระทบกับคลาสที่ implement interface เหล่านั้น

    🤙 ทางเลือก

    เราสามารถนำ Framework พวก Dependency Injection (DI) เข้ามาใช้แทนได้นะจ๊ะ โค้ดกระชับหลับสบายเต็มตื่นด้วย

    ข้อควรระวัง อย่านำ Abstract Factory Pattern ไปใช้มั่วซั่ว เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะใช้คำสั่ง new แบบปรกติ ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    Factory Method

    แนวคิดในการสร้าง object ที่เหมาะสมกับสถานะการณ์ที่กำลังเป็นอยู่

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

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช

    public class BankAccount
    {
        private bool isClosed;
        private double balance;
    
        public bool IsClosed { get => isClosed; }
        public double Balance
        {
            get => balance;
            protected set => balance = value;
        }
        public string OwnerName { get; set; }
    
        public void Deposit(double amount)
        {
            if (amount > 0)
            {
                balance += amount;
            }
        }
    
        public virtual void Withdraw(double amount)
        {
            if (amount <= balance)
            {
                balance -= amount;
            }
        }
    
        public void CloseAccount()
        {
            isClosed = true;
        }
    }
    public class SavingAccount : BankAccount
    {
    }
    public class CurrentAccount : BankAccount
    {
        private double credit = 1000;
    
        public override void Withdraw(double amount)
        {
            if (amount <= Balance + credit)
            {
                Balance -= amount;
            }
        }
    }
    static void Main(string[] args)
    {
        var sa = new SavingAccount();
        sa.OwnerName = "(Saving) Saladpuk";
        sa.Deposit(500);
        sa.Withdraw(700);
        Console.WriteLine($"{sa.OwnerName}, has THB {sa.Balance}.");
    
        var ca = new CurrentAccount();
        ca.OwnerName = "(Current) Saladpuk";
        ca.Deposit(700);
        ca.Withdraw(1000);
        Console.WriteLine($"{ca.OwnerName}, has THB {ca.Balance}.");
    }
    public class SavingAccount
    {
        private double balance;
        public double Balance { get => balance; }
        public string OwnerName { get; set; }
    
        public void Deposit(double amount)
        {
            if (amount > 0)
            {
                balance += amount;
            }
        }
    }
    public class CurrentAccount
    {
        private double balance;
        public double Balance { get => balance; }
        public string OwnerName { get; set; }
    
        public void Deposit(double amount)
        {
            if (amount > 0)
            {
                balance += amount;
            }
        }
    }
    public class BankAccount
    {
    }
    public class BankAccount
    {
        private double balance;
        public double Balance { get => balance; }
        public string OwnerName { get; set; }
    
        public void Deposit(double amount)
        {
            if (amount > 0)
            {
                balance += amount;
            }
        }
    }
    public class SavingAccount : BankAccount
    {
    }
    
    public class CurrentAccount : BankAccount
    {
    }
    var sa = new SavingAccount();
    sa.OwnerName = "(Saving) Saladpuk";
    sa.Deposit(500);
    Console.WriteLine($"{sa.OwnerName}, has THB {sa.Balance}.");
    
    var ca = new CurrentAccount();
    ca.OwnerName = "(Current) Saladpuk";
    ca.Deposit(700);
    Console.WriteLine($"{ca.OwnerName}, has THB {ca.Balance}.");
    var sa = new SavingAccount();
    sa.OwnerName = "(Saving) Saladpuk";
    sa.Deposit(500);
    public class BankAccount
    {
        ...
    
        public double Balance
        {
            get => balance;
            protected set => balance = value;
        }
    
        public virtual void Withdraw(double amount)
        {
            if (amount <= balance)
            {
                balance -= amount;
            }
        }
    }
    public class CurrentAccount : BankAccount
    {
        private double credit = 1000;
        public override void Withdraw(double amount)
        {
            if (amount <= Balance + credit)
            {
                Balance -= amount;
            }
        }
    }
    static void Main(string[] args)
    {
        var sa = new SavingAccount();
        sa.OwnerName = "(Saving) Saladpuk";
        sa.Deposit(500);
        sa.Withdraw(700);
        Console.WriteLine($"{sa.OwnerName}, has THB {sa.Balance}.");
    
        var ca = new CurrentAccount();
        ca.OwnerName = "(Current) Saladpuk";
        ca.Deposit(700);
        ca.Withdraw(1000);
        Console.WriteLine($"{ca.OwnerName}, has THB {ca.Balance}.");
    }
    public class BankAccount
    {
        ...
    
        private bool isClosed;
        public bool IsClosed { get => isClosed; }
    
        public void CloseAccount()
        {
            isClosed = true;
        }
    }
    static void Main(string[] args)
    {
        var acc = new BankAccount();
        acc.OwnerName = "Saladpuk";
        acc.Deposit(500);
    
        var result = acc.ToString();
        Console.WriteLine(result);
    }
     public struct Something
     {
         public int X { get; set; }
         
         public override string ToString()
         {
             X = 77.9;
             return $"The value of X is: {X}";
         }
     }
    public struct Something
     {
         public int X { get; set; }
         
         public readonly override string ToString()
         {
             // X = 77.9; ต้องเอาบรรทัดนี้ออกไม่งั้น compile ไม่ผ่าน
             return $"The value of X is: {X}";
         }
     }
    public interface ICalculator
    {
        int Add(int a, int b);
    }
    public interface ICalculator
    {
        int Add(int a, int b)
        {
            return a + b;
        }
    }
    public enum MainColor
    {
        Red,
        Green,
        Blue
    }
    public static RGBColor FromMainColor(MainColor colorBand)
    {
        switch (colorBand)
        {
            case Rainbow.Red:
                return new RGBColor(0xFF, 0x00, 0x00);
            case Rainbow.Green:
                return new RGBColor(0x00, 0xFF, 0x00);
            case Rainbow.Blue:
                return new RGBColor(0x00, 0x00, 0xFF);
            default:
                throw new ArgumentException();
        };
    }
    public static RGBColor FromMainColor(MainColor colorBand) =>
        colorBand switch
        {
            Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
            Rainbow.Green  => new RGBColor(0x00, 0xFF, 0x00),
            Rainbow.Blue   => new RGBColor(0x00, 0x00, 0xFF),
            _              => throw new ArgumentException(),
        };
    public string GetStateCode(Address location) =>
        location switch
        {
            { State: "BKK" } => "10",
            { State: "KKN" } => "40",
            { State: "UBN" } => "76",
            _ => string.Empty
        };
    public string checkGameResult(string first, string second)
        => (first, second) switch
        {
            ("rock", "paper") => "Lose",
            ("rock", "scissors") => "Win",
            ("paper", "rock") => "Win",
            ("paper", "scissors") => "Lose",
            ("scissors", "rock") => "Lose",
            ("scissors", "paper") => "Win",
            (_, _) => "tie"
        };
    public string Describe(object obj)
    {
        switch(obj)
        {
            case Rectangle r when r.Length == 10 && r.Width == 10:
                return "Found 10x10 rectangle";
            ...
        }
    }
    public string Describe(object obj)
    {
        switch(obj)
        {
            case Rectangle (10, 10): 
                return "Found 10x10 rectangle";
            ...
        }
    }
    switch( p.FirstName, p.MiddleName, p.LastName)
    {
        case (string f, string m, string l):
            return $"{f} {m[0]}. {l}";
            
        case (string f, null, string l):
            return $"{f} {l}";
        ...
    }
    public void WriteSomeThing()
    {
        using (var writer = new StreamWriter("file.txt"))
        {
            ...
        } // ตัวแปร writer จะเรียก Dispose ที่นี่
        ...
    }
    public void WriteSomeThing()
    {
        using var writer = new StreamWriter("file.txt");
        ...
        // ตัวแปร writer จะเรียก Dispose ที่นี่
    }
    public void SomeMethod()
    {
        int a = 7;
        
        void LocalFunction() => a = 0;
    }
    public void SomeMethod()
    {
        int a = 7;
        
        static void LocalFunction(int b) => b = 0;
    }
    ref struct Something
    {
    }
    ref struct Something
    {
        public void Dispose()
        {
        }
    }
    using (var st = new Something())
    {
        ...
    }
    #nullable enable
    public class Student
    {
        public string FirstName { get; set; }
        public string? LastName { get; set; } // เป็น null ได้
    }
    public async IAsyncEnumerable<int> GenerateSequence()
    {
        for(var i = 1; i <= 20; i++)
        {
            await Task.Delay(100);
            yield return i;
        }
    }
    await foreach (var number in GenerateSequence())
    {
        Console.WriteLine(number);
    }
    var words = new string[]
    {
                    // index from start    index from end
        "The",      // 0                   ^9
        "quick",    // 1                   ^8
        "brown",    // 2                   ^7
        "fox",      // 3                   ^6
        "jumped",   // 4                   ^5
        "over",     // 5                   ^4
        "the",      // 6                   ^3
        "lazy",     // 7                   ^2
        "dog"       // 8                   ^1
    };              // 9 (words.Length)    ^0
    Console.WriteLine($"The last word is {words[^1]}");
    // "dog"
    var quickBrownFox = words[1..4];
    // { quick, brown, fox }
    var lazyDog = words[^2..^0];
    // { lazy, dog }
    var allWords = words[..]; // เอาข้อมูลมาทั้งหมดเลย
    var firstPhrase = words[..4]; // เอาตั้งแต่ index แรกถึง index 4
    var lastPhrase = words[6..]; // เอาตั้งแต่ index 6 ถึงตัวสุดท้าย
    Range phrase = 1..4;
    var text = words[phrase];
    IEnumerable<int> numbers = null;
    if (numbers == null)
    {
        numbers = Enumerable.Empty<int>();
    }
    IEnumerable<int> numbers = null;
    numbers ??= Enumerable.Empty<int>();
    public struct Coords<T>
    {
        public T X;
        public T Y;
    }
    Span<Coords<int>> coordinates = stackalloc[]
    {
        new Coords<int> { X = 0, Y = 0 },
        new Coords<int> { X = 0, Y = 3 },
        new Coords<int> { X = 4, Y = 0 }
    };
     var fileName = test.jpg;
     var path1 = $@"c:\{filename}";
     var path2 = @$"c:\{filename}";
    var result1 = longOnGoalAPI.GetMatchResult(1234);
    var result2 = lifeScoreAPI.GetHistory(DateTime.Now, "ManU", "Liver");
    var homeScoreFromResult1 = // หาคะแนนของทีมเหย้า จาก LongOnGoalAPI
    var awayScoreFromResult1 = // หาคะแนนของทีมเยือน จาก LongOnGoalAPI
    
    var homeScoreFromResult2 = // หาคะแนนของทีมเหย้า จาก LifeScoreAPI
    var awayScoreFromResult2 = // หาคะแนนของทีมเยือน จาก LifeScoreAPI

    Factory Method Pattern จะสร้าง object โดยไม่ได้มีเรื่องเซตมาเกี่ยวข้อง

  • Abstract Factory Pattern จะสร้าง object โดยคำนึงถึงเรื่องเซตด้วยเสมอ และ มันจะต้องสามารถสร้าง product อื่นๆที่อยู่ภายในเซตเหล่านั้นได้ด้วย

  • Mr.Saladpuk
    Open & Close Principle
    Single-Responsibility Principle
    Program to an interface and not to an implementation.
    Dependency-Inversion Principle
    🏭 Factory Method Pattern
    Abstraction
    Encapsulation
    Factory Method Pattern 🤔 ทำไมต้องใช้ด้วย ?
    Mr.Saladpuk
    แบบนี้มันไม่ใช่การออกแบบที่ดีนะ แต่ขอเน้นไปที่เรื่อง Design Pattern ก่อน
    เวลาที่มีแผนที่ หรือ monster ใหม่ๆเข้ามา มันจะทำให้ MonsterFactory ใหญ่ขึ้นเรื่อยๆ
    ของต่างประเภทกัน ก็อาจมีความสัมพันธ์ที่จัดไว้ในเซตเดียวกันได้นะ
    รู้สึกว่า เสือ มันไม่เข้าพวกไหม ? ... เพราะมันมาจากคนละเซตยังไงล่ะ

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

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย Mr.Saladpuk และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    🧐 โจทย์

    สมมุติว่าเราเขียนเกมที่มีแผนที่ 2 แบบคือแบบ ป่า (Payon) และ ทะเลทราย (Desert) ตามรูป

    ซึ่งภายในแผนที่นั้นจะมีตัว Slime น้อยน่ารักเป็น monster เดินอยู่ภายในแผนที่เหล่านั้น แต่ความวุ่นวายของมันก็คือ

    • Slime ที่อยู่ใน Payon นั้นจะเป็นตัวสีเขียว ชื่อว่า Poporing ที่มีความสามารถในการปล่อยพิษได้

    • Slime ที่อยู่ใน Desert จะเป็นตัวสีเหลือง ชื่อว่า Drops ที่สามารถปล่อยไฟได้

    • แผนที่ Payon ถ้าเป็นเวลา 18:00 ตรงเราจะเจอบอส Slime ที่ชื่อว่า Angeling

    • แผนที่ Desert ถ้าเป็นเวลา 19:00 ตรงเราจะเจอบอส Slime ที่ชื่อว่า Ghostring

    • และแผนที่ทั้ง 2 ถ้าเป็นเวลา 12:00 เราจะได้ Slime ที่ชื่อว่า Poring ซึ่งถ้ามันเกิดในแผนที่ Payon มันจะปล่อยพิษได้ แต่ถ้ามันเกิดในแผนที่ Desert มันจะปล่อยไฟได้

    ซึ่งทั้งหมดที่ว่ามาก็จะสรุปเป็นภาพคร่าวๆได้ประมาณนี้

    แล้วเราจะเขียนโค้ดกันยังไงดีล่ะ เพื่อให้แผนที่ทั้ง 2 แบบ สามารถสร้าง Slime ออกมาได้ถูกตามเงื่อนไข และ โค้ดจะต้องมีความยืดหยุ่นสูงด้วยนะ ?

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

    จากที่อ่านมาก็ดูเหมือนมันจะไม่ยากอะไรเลยชิมิ? เจ้าพวก Slime นั้นก็น่าจะเป็นกลุ่มตระกูลเดียวกันดังนั้นก็น่าจะอยู่ในรูปนี้ได้

    ส่วนตอนสร้าง Slime ก็แค่สร้างเงื่อนไขว่า ตอนนี้อยู่แผนที่อะไร และ เป็นเวลาเท่าไหร่ เพียงเท่านี้เราก็จะสามารถสร้าง Slime ได้ถูกต้องตามเงื่อนไขที่ว่ามาละ ตามโค้ดด้านล่างเบย (ขอเขียนเวลาเป็น string นะจะได้เข้าใจง่ายๆหน่อย)

    ซึ่งจากโค้ดด้านบนก็ไม่ได้มีอะไรผิดนะ สามารถทำงานได้ถูกต้องตามโจทย์เลย แต่จะเกิดอะไรขึ้นถ้า Slime ที่อยู่ในแผนที่มันมีมากกว่าแค่ 4 แบบที่เราเห็น? หรือเงื่อนไขเวลามันเปลี่ยนแปลง? ... เราก็ต้องไปไล่แก้โค้ดใหม่อะดิ 😨

    Open & Close Principle (OCP) อันนี้เป็นตัวอย่างในการออกแบบที่ละเมิดหลักในการออกแบบที่ชื่อว่า OCP นั่นเอง ซึ่งมันทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สังเกตุได้ว่าถ้ามี Slime แบบใหม่เข้ามา หรือมีแผนที่แบบอื่นๆอีก เราก็จะต้องไปไล่แก้เจ้าพวก IF-ELSE ที่อยู่ด้านบนกันใหม่ทุกครั้งนั่นเอง

    แนะนำให้อ่าน สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้ Open & Close Principle

    ถ้าใครคิดตามแล้วได้คำตอบออกมาเหมือนในโค้ดด้านบน ก็ไม่ต้องตกใจอะไรนะ เพราะจริงๆเจ้าโค้ดด้านบนนั้นมันก็คือ โรงงานผลิตอย่างง่าย ของ pattern ตัวนี้ ซึ่งเรามักเรียกมันว่า Simple Factory นั่นเอง โดยเจ้ารูปแบบนี้มันจะยังไม่สมบูรณ์เพราะมันยังขัดกับหลักการออกแบบหลายๆอย่างอยู่นั่นเอง

    ก่อนที่จะไปทำให้มันสมบูรณ์ ผมจะขอแยกเจ้าเมธอด CreateNewSlime() ออกมาเป็น Model ที่ใช้ในการผลิต Slime ก่อนดีกว่า ซึ่งภาพด้านล่างคือโครงสร้างของ Simple Factory นั่นเอง

    Simple Factory หรือ รูปแบบอย่างง่ายของ Factory Pattern ที่ยังไม่สมบูรณ์นั่นเอง

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

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

    จากปัญหาที่ว่ามาเราจะพบว่า ทุกครั้งที่มีเงื่อนไขใหม่ มี Slime แบบใหม่ มีแผนที่แบบใหม่ หรืออะไรก็ตาม มันจะทำให้ เราต้องไปแก้เจ้าคลาส SimpleSlimeFactory เสมอ เลยทำให้ในอนาคตมันจะ บวมฉ่ำ อย่างไม่ต้องสงสัยเลย

    SimpleSlimeFactory จะบวมขึ้นไปเรื่อยๆถ้า Slime เพิ่มขึ้น หรือ แผนที่เพิ่มขึ้น

    ส่วนสาเหตุที่มันบวมก็เพราะว่า มันดูแลเรื่องจุกจิกจากหลายเรื่อง เช่น ต้องคอยรับแผนที่ใหม่ๆ ต้องคอยรับ Slime แบบใหม่ๆ และไหนจะเงื่อนไขจุกจิกอย่างเวลาในแต่ละแผนที่อีกนั่นเอง

    ดังนั้นเพื่อที่จะแก้ปัญหาการบวมฉ่ำของมัน เราก็ควรที่จะแยกเรื่องจุกจิกออกเป็นเรื่องๆ โดยถ้ามองจากรูปด้านบนเราก็จะสามารถแบ่งออกมาได้เป็น 2 กลุ่มคือ แผนที่ กับ Slime นั่นเอง

    และถ้าเราดูความสัมพันธ์ของเจ้า 2 อย่างนี้ดีๆเราจะพบว่า แผนที่เป็นตัวกำหนดว่าจะสร้าง Slime แบบไหน นั่นเอง และรวมถึงความสามารถของ Slime ด้วย ตามรูปด้านล่างเบย

    🔥 แก้ไขปัญหา

    จากที่ร่ายมาทั้งหมดเราจะสามารถหา พฤติกรรม + หน้าที่การรับผิดชอบ ออกมาได้ว่า

    • แผนที่ มีหน้าที่รับผิดชอบในการ สร้าง Slime

    • แผนที่ ต้องเป็นคน ดูแลเงื่อนไขต่างๆ ในการสร้าง Slime (เช่น เวลา, ความสามารถพิเศษ)

    • Slime เป็นแค่ ผลลัพท์ ที่เราอยากได้เพียงแค่นั้น

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

    ซึ่งแต่ละโรงงานก็จะมีหน้าที่คอยดูแลการผลิต Slime ของใครของมันเอง ซึ่งตรงตามหลัก SRP อีกด้วย

    Single-Responsibility Principle (SRP) เจ้าสิ่งนั้นๆควรมีหน้าที่รับผิดชอบเพียงอย่างเดียว ซึ่งเจ้าโรงงานแต่ละโรงงานด้านบน มันก็จะดูแลแค่หน้าที่การผลิต Slime เพียงอย่างเดียวเท่านั้น ซึ่งถ้าเงื่อนไขในการผลิต Slime ของโรงงาน Desert เปลี่ยนก็ไม่ไปกระทบอะไรกับโรงงาน Payon นั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย Single-Responsibility Principle

    แต่มันยังไม่จบเท่านั้น เพราะมันยังขัดกับ OCP (ปัญหาตัวแรกที่อยู่ด้านบน) อยู่เหมือนเดิม เพราะเวลามีอะไรใหม่ๆเข้ามา เราก็ต้องไปไล่แก้โค้ดเก่าของเราอยู่ดี ... แต่ถ้าเรามาคิดดีๆ เราก็จะพบว่า Payon และ Desert นั้นมันก็ เป็นโรงงานทั้งคู่ อยู่แล้ว ดังนั้นมันก็น่าจะรวมร่างเป็นแบบนี้ได้นะ

    แต่จากรูปด้านบนจะเห็นว่าเมธอด CreateNewSlime() ที่อยู่ใน Base class (เส้นสีแดง) นั้น มันไม่รู้ว่าจะต้องสร้าง Slime จริงๆยังไง เพราะ Sub class ของมันต่างหาก (เส้นสีเขียว) คือตัวที่รู้ว่าในสถานะการณ์นี้จะต้องสร้าง Slime อะไรออกมา ดังนั้น Base class ควรจะปล่อยให้ Sub class ทำหน้าที่ในการสร้าง Slime ไปซะ เลยทำให้เราต้องเพิ่ม Abstract method เข้ามาอีกตัวให้ sub class รับผิดชอบเรื่องการสร้าง slime นั่นเอง

    เพียงเท่านี้เราก็สามารถแก้ปัญหาโจทย์นี้ได้เรียบร้อยแล้ว แถมเมื่อเราทำงานกับโรงงานพวกนี้ เรายังสามารถใช้ Best Practice ในเรื่องของ Program to an interface and not to an implementation. ได้อีกด้วย เพราะเราไม่ได้ทำงานกับระดับ Implementation แล้วยังไงล่ะ ซึ่งมันก็เป็นการเข้าข่ายกับ DIP ไปด้วยในตัวนั่นเอง เย่ๆ

    Dependency-Inversion Principle (DIP) เป็นหนึ่งในหัวใจของการออกแบบให้เราไม่ไปผูกการทำงานไว้กับ Low level module นั่นเอง ส่วนใครที่อยากศึกษาเรื่องนี้เพิ่มเติมก็สามารถกดไปอ่านได้จากลิงค์นี้เบย Dependency-Inversion Principle

    ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า Factory Method Pattern เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏

    🤔 Factory Method คือไย ?

    🔥 จุดกำเหนิด

    ในบางทีการสร้าง object นั้นมันก็ไม่ได้ง่ายเลย เช่น constructor มันรับหลายๆ parameters ก็ปวดหัวละ หรือถ้ามีการสร้าง object ที่สร้างยากๆตัวเดียวกันหลายๆจุดขึ้นมา มันก็แสดงว่าเราก็จะมีโค้ดแบบเดียวกันอยู่ซ้ำๆหลายที่เต็มไปหมด และการที่เราไปสร้าง object เองในบางทีก็อาจทำให้ Abstraction + Encapsulation ที่วางไว้เสียหายโดยไม่ได้ตั้งใจก็เป็นได้

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

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

    🔥 วิธีการใช้

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

    object อะไรก็ตามที่เราอยากจะสร้าง เราจะเรียกมันว่า Product ซึ่งโดยปรกติเราก็จะมี product หลายๆแบบ เลยต้องทำมันเป็น Interface เอาไว้ (ตรงจุดนี้จริงๆจะเป็นอะไรก็ได้นะ ขอแค่เป็น abstraction ก็พอ)

    ส่วนคลาส Product ที่แท้จริงนั้นก็จะไป implement IProduct อีกที ซึ่งโดยปรกติเราจะเรียกคลาสที่แท้จริงเหล่านั้นว่า Concrete class นั่นเอง ดังนั้นในกรณีนี้ผมจะเรียกมันว่า ConcreateProduct ละกัน

    ส่วนตัวที่ทำหน้าที่สร้าง object เราจะเรียกมันว่า Factory ซึ่งโดยปรกติ เจ้าตัวนี้มันจะไม่รู้หรอกว่ามันต้องสร้าง Product อะไรออกมา มันมีหน้าที่เป็นแค่โครงให้คนอื่นมาเรียกใช้มันเท่านั้น มันเลยเป็นได้แค่ Abstract class ซึ่งมันจะมี Abstract method ไว้ให้ตัว Factory ที่รู้ว่าจะสร้าง Product อะไรบ้าง เป็นคนมากำหนดการทำงานนั่นเอง

    ดังนั้นสุดท้าย Factory ที่แท้จริง หรือ ConcreteFactory ก็จะเป็นคนมากำหนดว่าจะสร้าง Product ที่แท้จริงอะไรนั่นเอง

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

    ไหนลองเอาที่เราออกแบบมาเทียบกันดูดิ๊ ... เหมือนกันเปี๊ยบเบย

    ข้อแนะนำ ของทุกอย่างใน Design Patterns ทุกตัว เราไม่จำเป็นต้องทำตาม หรือ มีครบเหมือนตามที่เขาบอกไว้ก็ได้ (ถ้าเข้าใจ + มีเหตุผลที่ดีพอ) เพราะสิ่งที่ Pattern แต่ละตัวต้องการจะบอกเราคือ แนวทาง และเหตุผลในการออกแบบเพียงเท่านั้น ซึ่งสิ่งที่เราต้องทำต่อก็คือนำมันไปประยุกต์ให้เข้ากับปัญหาที่เราเจออยู่ให้เหมาะสมนั่นเอง

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

    ข้อดีที่สุดของการนำ Factory Method Pattern มาใช้ก็คือ มันลดการผูกกันของโค้ด (decoupling) นั่นเอง เพราะถ้าเราเขียนสร้าง object โดยใช้คำสั่ง new แบบเดิม มันจะมีปัญหาหลายๆอย่างตามมา ซึ่งผมขอยกตัวอย่างให้เห็นภาพเป็นตามด้านล่างละกัน

    😱 Hardcode

    สมมุติว่าเราเขียนโค้ดเป็นแบบด้านล่างนี้ละกัน

    ซึ่งดูแล้วก็เหมือนจะไม่ผิดบาปอะไรใช่ไหม แต่จริงๆมันหมายความว่า โค้ดของเราต้องทำงานกับคลาส Dog เท่านั้น!! ไม่มีวันเปลี่ยนเป็นอย่างอื่นเลย ถ้าอยากเปลี่ยนให้มันไปทำงานกับคลาส Cat แล้วล่ะก็ เราก็ต้อง Build & Compile & Deploy ใหม่ทุกครั้งที่มีการแก้โดยเลี่ยงไม่ได้เลย

    😱 Dependencies

    สมมุติว่าเราต้องไปสร้าง object ของคลาสด้านล่างนี้ โดยที่มันมี Constructor หน้าตาประมาณนี้

    เพียงแค่เห็น parameters ที่เราต้องส่งให้มันก็น่าจะหนาวแล้ว แต่ประเด็นไม่ใช่ที่จำนวน parameters ของมันนะ เพราะสิ่งที่น่ากลัวคือ Dependencies ของมันต่างหาก ลองนึกดูว่าจะสร้างเจ้าตัวนี้ทีนึง โค้ดง่ายๆของเราก็อาจจะซับซ้อนและมีของต่างๆที่ไม่เกี่ยวข้องกับมันเข้ามันเต็มไปหมดทั้งๆที่ตรงจุดนั้นไม่ได้จะใช้เลยด้วยซ้ำ!!

    😱 เทสยาก

    สมมุติว่าโค้ดของเราเขียนเป็นแบบนี้ละกัน

    คำถามคือแล้วเวลาที่เราจะเทสมันล่ะ เราจะต้องยิงไปที่ตัว database จริงเพื่อเทสเลยเหรอ? หรือถ้าเราจะ Mock มันล่ะจะทำยังไง?

    เขียนจนหน้ากระดาษจะเต็มอยู่ละ เพียงเท่านี้ก็น่าจะเห็นข้อเสียที่ซ่อนอยู่กับการใช้คำสั่ง new ตรงๆกันแล้วนะว่ามันจะนำพาความสนุกอะไรมาให้เราบ้าง ลองไปคิดต่อเอาดูก็น่าจะได้อีกหลายเหตุผลเลยเพราะเจ้า Coupling ตัวเดียวนี่แหละ

    🤔 ใช้แล้วดียังไง ?

    อย่างที่บอกว่า Design Pattern ตัวนี้ มันจะทำให้โค้ดของเรา มันลดการผูกกันของโค้ด (decoupling) ดังนั้นปัญหาที่ว่าด้านบนมันจะละลายหายไปจนหมดเลย ตามนี้

    👨‍🔧 Hardcode

    จากเดิมแทนที่เราจะใช้คำสั่ง new ไปสร้าง object ให้มันผูกติดกับคลาสนั้นๆเอง มันก็จะเปลายเป็นแบบนี้

    ซึ่งจากโค้ดใหม่นี้จะเห็นว่า ไม่มีการผูกติดกันอีกแล้ว มันจะสร้าง object อะไรมาให้เราก็ได้ ขึ้นกับความเหมาะสมของคลาสลูกนั่นเอง

    👨‍🔧 Dependencies

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

    👨‍🔧 เทสยาก

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

    🎯 บทสรุป

    👍 ข้อดี

    การนำ Factory Method Pattern มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง ทำให้เราสามารถเปลี่ยนแปลง แก้ไข รองรับสิ่งต่างๆได้มากขึ้น และมันยังช่วยเปิดให้เราทำพวก Inversion of Control (IoC) ได้ง่ายขึ้นด้วย

    👎 ข้อเสีย

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

    🤙 ทางเลือก

    เราสามารถนำ Framework พวก Dependency Injection (DI) เข้ามาใช้แทนได้นะจ๊ะ โค้ดกระชับหลับสบายเต็มตื่นด้วย

    ข้อควรระวัง อย่านำ Factory Method Pattern ไปใช้มั่วซั่ว เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะใช้คำสั่ง new แบบปรกติ ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย Mr.Saladpuk และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    Creational Patterns
    👦 Design Patterns
    public Slime CreateASlime(string mapName)
    {
        if(mapName == "payon")
        {
            return new Poporing();
        }
        else
        {
            return new Drops();
        }
    }
    
    public Wolf CreateAWolf(string mapName)
    {
        if(mapName == "payon")
        {
            return new WildWolf();
        }
        else
        {
            return new DesertWolf();
        }
    }
    
    // ที่เหลือไปคิดต่อเองคล้ายๆด้านบนแหละ
    var dog = new Dog();
    var cat = new Cat();
    var pig = new Pig();
    var tiger = new Tiger();
    var dog = flatArtFactory.CreateADog();
    var cat = flatArtFactory.CreateACat();
    var pig = flatArtFactory.CreateAPig();
    var tiger = flatArtFactory.CreateATiger();
    public abstract class SlimeFactory
    {
        public Slime CreateNewSlime(string currentTime)
        {
            return BuildASlime(currentTime);
        }
    
        protected abstract Slime BuildASlime(string currentTime);
    }
    public class PayonSlimeFactory : SlimeFactory
    {
        public override Slime BuildASlime(string currentTime)
        {
            if(currentTime == "12:00")
            {
                return new Poring("Poison");
            }
            else if(currentTime == "18:00")
            {
                return new Angeling();
            }
            else
            {
                return new Poporing();
            }
        }
    }
    public class DesertSlimeFactory : SlimeFactory
    {
        public override Slime BuildASlime(string currentTime)
        {
            // โค้ดก็คล้ายๆกับ PayonSlimeFactory แหละลองไปคิดเล่นๆดู
        }
    }
    public Slime CreateNewSlime(string mapName, string currentTime)
    {
        if(mapName == "Payon")
        {
            if(currentTime == "12:00")
            {
                return new Poring("Poison");
            }
            else if(currentTime == "18:00")
            {
                return new Angeling();
            }
            else
            {
                return new Poporing();
            }
        }
        else
        {
            // โค้ดคล้ายๆด้านบนแหละลองคิดเล่นๆดู
        }
    }
    var animal = new Dog();
    public class DatabaseConnector
    {
        public DatabaseConnector(IConnection conn,
            IResource resource,
            IProxy proxy,
            ICancelationToken cancelation,
            ..และอื่นๆ..)
        {
            // ทำอะไรซักอย่างไม่ต้องไปสนใจ
        }
    }
    var dbConn = new MySqlConnector();
    var animal = animalFactory.GetAnAnimal();
    var dbConn = dbConnectorFactory.CreateConnector();
    var dbConn = dbConnectorFactory.CreateConnector();
    
    // เล่นตามเกมก็ได้
    public class MockDbConnectorFactory : DbConnector { ... }
    
    // หรือจะส่ง mock object เข้าไปแทนที่ผ่าน constructor ก็ได้
    Constructor( mockFactory )

    LINQ

    🤔 ทำงานกับข้อมูลมหาศาลใน .NET เขาทำกันยังไงนะ (สาย .NET ไม่รู้ไม่ได้)

    ในบทนี้เราจะมาทำความรู้จักกับหนึ่งในความสามารถที่ทรงพลังที่สุดของภาษา C# เลยก็ได้ว่า นั่นก็คือเจ้าสิ่งที่เรียกว่า LINQ ซึ่งย่อมาจาก Language Integrated Query นั่นเอง โดยเจ้าตัวนี้เป็นหนึ่งมหากาพย์ที่ทำให้เราลดโค้ดจากร้อยๆบรรทัดให้เหลือแค่เพียงไม่กี่บรรทัดได้ และยังช่วยให้โค้ดที่เขียนกลายเป็น Clean Code อีกด้วยนะ เราลองไปทำความเข้าใจเรื่องของ LINQ กันเลยเลยดีกว่าครัช

    ถ้าคิดว่าใช้งาน LINQ คล่องแล้ว และรู้จักการทำงานแบบ Declarative กับ Imperative ของ LINQ และการทำ Chain แล้วละก็ข้ามเรื่องนี้ไปได้เลย

    LINQ มีหลายคนเลยสงสัยว่ามันออกเสียงว่ายังไง เจ้าตัวนี้ออกเสียงว่า ลิงค์ ครับ (อ้างจากสำเนียงเมกา) ไม่ได้ออกเสียงว่า ลิน หรือ ลินคิว ใดๆทั้งสิ้น จำง่ายๆว่ามันออกเสียงเหมือน link ที่ไม่ออกเสียงตัว k อ่ะ

    🤔 LINQ คือไรหว่า ?

    แบบสั้นๆก่อนเจ้า LINQ คือ ชุดคำสั่งที่จะทำให้เราทำงานกับกลุ่มของข้อมูลได้ง่ายๆ เช่น ทำงานกับ ข้อมูลที่ดึงมาจากฐานข้อมูล ทำงานกับ XML หรือพวก collection ต่างๆ โดยมีภาษาที่ใกล้เคียงกับ SQL syntax นั่นเอง

    🤔 LINQ ใช้ไงหว่า ?

    ซึ่งจากประสบการณ์ที่ผมไปสอนมาพบว่า เราไปดูตัวอย่างกันแล้วจะเข้าใจ LINQ ได้เร็วกว่าอ่านทฤษฎีครับ ดังนั้นผมจะใช้ตัวอย่างนี้อธิบายเอานะ โดยโจทย์ของผมคือถ้าผมมีข้อมูลตัวเลข 1~7 อยู่ใน array ตามโค้ดด้านล่าง

    แล้วถ้าเกิดผมอยากดึงเฉพาะเลขคู่ออกมาจากตัวแปร numbers ล่ะต้องทำยังไง ? ซึ่งถ้าเขียนโค้ดแบบปรกติเราก็จะเขียนออกมาได้ราวๆนี้

    🔥 เขียนแบบปรกติ

    ผมก็จะสร้าง array ขึ้นใหม่ เพื่อเอาไว้เก็บค่าเฉพาะเลขคู่ไว้ยังไงล่ะ ตามโค้ดด้านล่างเลย

    🔥 เขียนแบบใช้ LINQ

    คราวนี้เราก็จะมาลองเอา LINQ มาแก้โจทย์เดียวกันดูบ้างนะ ซึ่งการที่จะใช้ LINQ ได้นั้นเราจะต้องเรียกใช้ using System.Linq; ไว้ด้านบนสุดด้วยนะ และการเขียน LINQ เราสามารถเขียนได้ 2 วิธีตามนี้

    1.เขียน LINQ แบบเต็มๆ

    ตาไม่ได้ฝาดไปหรอกครับ โค้ดมันเหลือแค่นั้นจริงๆ และมันเขียนแทบจะเหมือนภาษา SQL syntax เลยยังไงล่ะ ดังนั้นใครที่เขียน SQL syntax เป็นอยู่แล้วรับรองครับว่าสบายเลย

    อธิบายโค้ดตามบรรทัด บรรทัดที่ 1 เราเลือกว่าจะทำงานกับกลุ่มข้อมูลตัวไหน ซึ่งในที่นี้คือ numbers นั่นเอง โดยข้อมูลแต่ละตัวในกลุ่มข้อมูลนั้นเราจะใช้ตัวแปรที่ชื่อว่า it เข้าไปไล่ค่ามัน (เหมือน foreach แหละ) บรรทัดที่ 2 เราทำการคัดกรองเอาเฉพาะข้อมูลตัวที่มันถูกหารด้วย 2 ลงตัวเท่านั้น ด้วยคำสั่ง where บรรทัดที่ 3 ข้อมูลไหนที่ผ่านเงื่อนไขจากบรรทัดที่ 2 เราจะทำการเอาข้อมูลเหล่านั้นมาใช้

    2.เขียน LINQ แบบย่อๆ ที่เห็นมันสั้นแล้วจริงๆมันยังเขียนให้กระชับลงได้อีกตามโค้ดด้านล่างเลย

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

    Clean Code จะเห็นว่าไม่ว่าจะเขียนแบบเต็มหรือแบบย่อนั้น มันอ่านง่ายสบายตากว่า การเขียนแบบปรกติเยอะม๊วกกกก ดังนั้นการทำงานอะไรก็แล้วแต่ที่ทำงานกับกลุ่มข้อมูล ผมแนะนำว่าให้ใช้ LINQ ไปเลยถ้าใช้ได้

    🤔 หัวใจของ LINQ มีไรบ้าง ?

    อยากเขียน LINQ เป็นจริงๆนั้นง่ายมาก เพราะ LINQ มีหัวใจการทำงานแค่ 3 เรื่องเท่านั้น โดยผมจะใช้โค้ดที่ใช้แก้โจทญืในตอนแรก มาเขียนกำกับหัวใจการทำงานลงไปละกัน ชึบๆ

    ซึ่งจากโค้ดด้านบนจะเห็นว่ามันมีการแบ่งงานออกเป็น 3 เรื่องคือ

    🔥 Data Source

    คือกลุ่มข้อมูลที่เราต้องการจะทำงานด้วย ซึ่งกลุ่มข้อมูลในที่นี้คืออะไรก็ได้ที่เป็นตระกูล collection ที่มาจาก IEnumerable นั้นเอง ซึ่ง array ก็เป็นหนึ่งในนั้น เราเลยสามารถใช้ LINQ ทำงานด้วยได้

    🔥 Query

    คือคำสั่งที่เราต้องการจะไปกระทำกับ Data Source ของเรา ซึ่งจากโค้ดตัวอย่างคือเราจะทำการ filter เอาเฉพาะข้อมูลตัวที่ 2 หารลงตัวนั่นเอง

    🔥 Query execution

    คือตัวสั่งให้ query ที่เราเตรียมไว้มันเริ่มทำงาน ซึ่งในโค้ดคือเจ้า foreach นั่นเอง มันจะเป็นตัวกระตุ้นให้โปรแกรมเริ่มวิ่งเข้าไปที่ data source เพื่อดึงข้อมูลมาตาม query ที่เขียนไว้

    Query Execution เจ้า query execution นี้มีการทำงานทั้งหมด 2 รูปแบบคือ Deferred Execution และ Forcing Immediate Execution เดี๋ยว ซึ่งทั้ง 2 แบบนี้จะต่างกันอย่างสิ้นเชิง และทำให้เหล่า developer สาย .NET ตกม้าตายมาเยอะแล้ว ซึ่งมันคือผมขอไปอธิบายไว้ด้านล่างๆครัช

    🤔 อยากเขียน LINQ ต้องทำไง ?

    เราก็แค่เรียนรู้ว่า LINQ มันสามารถเล่นอะไรกับกลุ่มของข้อมูลได้บ้างเพียงเท่านี้ก็ใช้ LINQ ได้เลย ส่วนถ้าอยากรีดศักยภาพมากขึ้นเราต้องเข้าใจการใช้ กับ ด้วยจะดีมาก ดังนั้นเราลองไปดูว่า LINQ ทำอะไรกับกลุ่มของข้อมูลได้บ้างกัน

    🔥 คัดกรองข้อมูล (Filtering)

    ถ้าเราอยากคัดกรองข้อมูลให้มันเอาเฉพาะของที่เราอยากได้เท่านั้นออกมา เราสามารถใช้คำสั่ง Where ในการคัดกรองได้ เช่น ถ้าเรามีโค้ดเป็นแบบนี้

    แล้วเราอยากได้เฉพาะตัวเลขที่มากกว่า 4 ขึ้นไป เราก็จะเขียนโค้ดออกมาเป็นตามนี้

    ผลลัพท์ { 5, 6, 7 }

    หรือเราอยากคัดกรองเอาเฉพาะ เลขคี่ ที่มากกว่า 2 ขึ้นไป

    ผลลัพท์ { 3, 5, 7 }

    🔥 เรียงลำดับ (Ordering)

    ในกรณีที่ data source ของเราไม่ได้เรียงลำดับมา เราสามารถทำให้มันเรียงลำดับให้เราได้ เช่นผมมี data source เป็นแบบนี้

    เรียงจากน้อยไปมาก

    ผลลัพท์ { 1, 2, 3, 4, 5, 6, 7 }

    เรียงจากมากไปหาน้อย

    ผลลัพท์ { 7, 6, 5, 4, 3, 2, 1 }

    🔥 จัดกลุ่ม (Grouping)

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

    ผมต้องการจะให้มันแบ่งออกเป็น 2 กลุ่มคือ กลุ่มเลขคู่ กับ กลุ่มเลขคี่ ก็จะเขียนออกมาได้ประมาณนี้ (ขี้เกียจเขียนแบบเต็มแล้วนะ)

    ผลลัพท์ กลุ่ม false จะมีข้อมูลเป็น 1, 3, 5, 7 กลุ่ม true จะมีข้อมูลเป็น 2, 4, 6

    เอาเท่านี้ก่อนละกัน เพราะไม่งั้นมันจะเยอะมากจนบทความนี้อาจจะอ่านแล้วกระตุกเลย ผมขอเอาคำสั่งทั้งหมดไปเขียนสรุปไว้ในหัวข้อถัดไปเลยละกัน

    🤔 LINQ ทำไรได้บ้าง ?

    ความสามารถแค่ส่วนหลักๆของ LINQ ที่เราจะได้ใช้กันขอสรุปเป็นตารางไว้แบบนี้ละกัน

    แนะนำให้อ่าน ตัวอย่างการทำงานจริงๆของแต่ละคำสั่งคืออะไร สามารถไปอ่านได้จากบทความนี้นะ

    คำสั่งที่เอาไว้ทำงานกับ collection

    กลุ่มนี้ทั้งหมดเป็น Deferred Execution - คืออะไรไปอ่านต่อได้จากด้านล่าง

    คำสั่งที่ได้ผลลัพท์กลับมาเลย

    กลุ่มนี้ทั้งหมดเป็น Forcing Immediate Execution - คืออะไรไปอ่านต่อได้จากด้านล่าง

    คำสั่งในการแปลง collection

    กลุ่มนี้ทั้งหมดเป็น Forcing Immediate Execution - คืออะไรไปอ่านต่อได้จากด้านล่าง

    💡 Deferred vs Immediate

    จากที่เคยอธิบายไปว่า LINQ มีการสั่ง execution ทั้งหมด 2 รูปแบบนั่นคือ Deferred Execution และ Forcing Immediate Execution ซึ่งทั้งสองตัวนี้แตกต่างกันสิ้นเชิง เพราะมันเกิดมาจาก 2 แนวคิดในการเขียนโค้ดนั่นเอง

    แนะนำให้อ่าน เจ้า 2 แนวคิดที่ว่านั่นคือ Functional Programming กับ Imperative Programming นั่นเอง ซึ่งสามารถอ่านมันได้เต็มๆได้จากลิงค์นี้เบย

    ซึ่งการทำงานของ LINQ มันจะได้ผลลัพท์กลับมาทั้งหมด 2 แบบ โดยแต่ละแบบทำงานกันแบบนี้

    🔥 Forcing Immediate Execution

    เป็นแบบที่เราคุ้นเคยกันที่สุด นั่นคือเป็นการสั่งออกไปแล้วได้ผลลัพท์กลับมาทันทีนั่นเอง (ดูกลุ่มคำสั่งนี้ได้จากตารางด้านบน) เช่น โค้ดด้านล่างนี้ เป็นการหาค่าสูงสุดของข้อมูลใน collection

    ผลลัพท์ result = 7

    🔥 Deferred Execution

    เป็นคำสั่งที่จะไม่ทำงานจนกว่าจะผ่านจุดที่เกิด Query Execution ขึ้นเท่านั้น ซึ่งคำสั่งตระกูลนี้จะได้ผลลัพท์กลับมาเป็น collection ของ IEnumerable<T> นั่นเอง (ดูกลุ่มคำสั่งนี้ได้จากตารางด้านบน) เช่นโค้ดตัวอย่างจะทำการดึงค่าเฉพาะเลขคู่ออกมาเท่านั้น แต่ผมจะเพิ่มว่าทุกๆ loop มันจะมีตัวนับเลขถูกเพิ่มค่าเข้าไปเรื่อยๆ ตามนี้

    คำถามคือ runner มีค่าเป็นเท่าไหร่ถ้าผม run โค้ดเพียงเท่านี้เป๊ะๆเลย ?

    เฉลย runner จะมีค่าเป็น 0 ครับ

    เพราะคำสั่งในกลุ่มนี้มันเป็นการจำว่ามันจะต้องไปทำอะไรกับ data source อย่างเดียวเท่านั้น มันจะไม่ดำเนินการอะไรเลย จนกว่ามันจะผ่าน Query Execution ซักตัวนั่นเอง

    จากที่ว่ามาผมก็เลยเพิ่ม query execution แบบง่ายๆเข้าไปนั่นคือ foreach แบบโง่ๆเลยตามนี้

    คำถามคือ ในบรรทัดที่ 3 กับบรรทัดที่ 7 มันจะโชว์เลขอะไรออกมา ?

    เฉลย Before Query Execution, Runner: 0 After Query Execution, Runner: 3

    สาเหตุที่บรรทัดที่ 3 มันโชว์เลข 0 เพราะค่า query มันยังไม่ผ่าน Query Execution นั่นเอง ส่วนบรรทัดที่ 7 ที่มีนโชว์เลข 3 เพราะมันผ่าน Query Execution แล้วนั่นเองมันเลยไปเพิ่มค่า runner ยังไงล่ะ

    🔥 Deferred Execution + Forcing Immediate Execution

    ถ้าในกรณีที่เราเขียนคำสั่งเป็น Deferred Execution แต่เราจบด้วย Forcing Immediate Execution แล้วล่ะก็ มันจะกลายเป็น Forcing Immediate Execution โดยทันที เช่นโค้ดเดิมด้านบน ผมเอามาเขียนให้มันจบโดยใช้คำสั่ง .Count() ซึ่งเป็นคำสั่งของ forcing immediate execution ผมจะได้ผลลัพท์ออกมาแบบนี้

    ผลลัพท์ Runner: 3

    💡 Streaming vs Non-Streaming

    ทุกๆครั้งที่เราไปดึงข้อมูลมาจาก data source เพื่อมาทำการประมวลผล มันจะมีการดึงข้อมูลมา 2 รูปแบบคือ

    🔥 Streaming

    เป็นการดึงข้อมูลมาแบบต่อเนื่อง โดยมันจะค่อยๆอ่านข้อมูลจาก data source มาทำการประมวลผลเรื่อยๆ ไม่ได้ดึงมาตูมเดียวทั้งหมดนั่นเอง ซึ่งเจ้าตัวนี้จะเหมาะใช้กับการทำงานกับ data source ที่มีขนาดใหญ่ๆ

    🔥 Non-Streaming

    เป็นการดึงข้อมูลมาตูมเดียวจบ แล้วทำการประมวลผลเลย

    🤔 มีไรที่ควรรู้อีกไหม ?

    เรื่องพื้นฐานสุดท้ายที่นึกออกละ คำสั่งพวก LINQ ทั้งหลายมันเป็น Extension Method ที่อยู่ใน namespace System.Linq; ดังนั้นมันหมายความว่าคำสั่ง LINQ มันเชื่อมกันได้ เช่น ผมมี Data Source เป็นตัวเลข 1-100 ตามนี้

    แล้วผมต้องการข้อมูล 4 กลุ่มตามนี้

    1. กลุ่มเลขคู่

    2. กลุ่มเลขคู่ที่ 5 หารลงตัว

    3. กลุ่มเลขคู่ที่ 7 หารลงตัว

    4. กลุ่มเลขคู่ที่ 5 และ 7 หารลงตัว

    เราก็จะสามารถใช้ความสามารถในการเชื่อมกันออกมาแบบนี้ได้

    จะเห็นว่ากลุ่มที่เป็นเลขคู่จะใช้เป็นตัวตั้งต้นตัวแรก แล้วที่เหลือจะเอาผลลัพท์ของตัวแรกมาใช้ต่อเรื่อยๆได้ หรือในข้อ 4 เราจะเขียน Chain กันในรูปแบบนี้ก็ได้เหมือนกัน

    🎯 บทสรุป

    LINQ เป็นมหากาพย์ตัวนึงที่ดูเหมือนว่ามันจะเยอะมาก แต่ถ้าเราเข้าใจมันทั้งหมดแล้วเราจะพบว่า มันไม่มีอะไรเลย และไม่ต้องไปนั่งไล่จำอะไรเลย ขอแค่รู้หัวใจหลัก 3 เรื่องของมันก็พอ Data Source Query Query Execution เท่านั้นเอง เพียงเท่านี้โค้ดของเราก็จะกระชับและทรงพลังมาก เพราะมันสามารถไปเชื่อมใช้งานกับสิ่งต่างๆได้อีกเยอะเลย เช่น ทำ Query Database ทำงานร่วมกับ Entity Framework หรือแม้กระทั่งการทำงานกับ Reactive เช่น Reactive Extension (Rx) ก็ยังได้ คือจริงๆมันสารพัดประโยชน์มากจริงๆนะเจ้าตัวนี้ จนแทบจะเรียกว่าใครเขียน C# หากินเป็นอาชีพไม่รู้ไม่ได้

    แนะนำให้อ่าน ถ้าเพื่อนๆอยากเข้าใจการทำงานจริงๆของ LINQ หรือดูตัวอย่างหลายๆแบบแล้วล่ะก็สามารถเข้าไปดูเพิ่มเติมได้จากลิงค์ด้านล่างนี้เลยครัช (เนื้อหาแน่นปึก) และนอกจากนี้ยังมีตัวอย่างของภาษาอื่นๆอีกนะเช่น VB

    มีอะไรใหม่บ้าง

    เวลาที่มีอัพเดทอะไรใหม่ๆจะเอามาใส่ไว้ในหน้านี้แหละ

    ✏️ ประวัติการอัพเดท

    สำหรับใครที่ไม่อยากพลาดอัพเดทบทความใหม่ๆ สามารถเข้าไปกด Like เพื่อรับข่าวสารใหม่ๆจาก ได้นะครับ 😍

    10/06/2021
    • เพิ่มบทความ Hash functions - ลองเปลี่ยนของธรรมดาให้เป็นฟามลับกันมุ้ย 😘

    09/06/2021

    • เพิ่มบทความ Security in actions - ลองเอา security มาเขียนใช้งานจริงกัน

    Facebook Mr.Saladpuk

    Collection

    Skip

    ข้าม

    Collection

    SkipWhile

    ข้ามจนกว่า

    Collection

    TakeWhile

    เอาจนกว่า

    Collection

    OrderBy

    เรียงลำดับ น้อย-มาก

    Collection

    OrderByDescending

    เรียงลำดับ มาก-น้อย

    Collection

    Reverse

    เรียงลำดับกลับด้าน

    Collection

    Union

    รวม 2 collection เข้าด้วยกัน

    Collection

    Intersect

    เอาเฉพาะตัวที่ซ้ำกันใน 2 collection

    Collection

    Except

    ตัดตัวที่ซ้ำกับ collection อื่น

    Collection

    number

    Average

    หาค่าเฉลี่ย

    number

    First

    เอาข้อมูลตัวแรก

    T

    FirstOrDefault

    เอาข้อมูลตัวแรก ถ้าไม่เจอขอ default

    T หรือ default

    Any

    ดูว่ามีซักตัวไหมที่ตรงเงื่อนไข

    bool

    All

    ทุกตัวตรงเงื่อนไขหรือไม่

    bool

    Contains

    ใน collection มีตัวนี้หรือเปล่า

    bool

    คำสั่ง

    ใช้สำหรับ

    ผลลัพท์

    Where

    กรองข้อมูล

    Collection

    Select

    เลือก

    Collection

    Distinct

    ตัดตัวซ้ำ

    Collection

    Take

    คำสั่ง

    ใช้สำหรับ

    ผลลัพท์

    Count

    นับว่ามีกี่ตัว

    number

    Sum

    หาผลรวม

    number

    Min

    หาค่าน้อยสุด

    number

    Max

    คำสั่ง

    ใช้สำหรับ

    ผลลัพท์

    ToArray

    แปลงเป็น Array<T>

    Array<T>

    ToList

    แปลงเป็น List<T>

    List<T>

    ToDictionary

    แปลงเป็น Dictionary<K, V>

    Dictionary<K, V>

    Lambda
    Generic
    พระคัมภีร์การใช้คำสั่ง LINQ
    Microsoft document - Functional vs Imperative
    Microsoft document - LINQ

    เอา

    หาค่ามากสุด

    var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };
    var evenIndex = 0;
    var evenNumbers = new int[3];
    for (int i = 0; i < numbers.Length; i++)
    {
        if (numbers[i] % 2 == 0)
        {
            evenNumbers[evenIndex++] = numbers[i];
        }
    }
    var evenNumbers = from it in numbers
                      where it % 2 == 0
                      select it;
    var evenNumbers = numbers.Where(it => it % 2 == 0);
    // 1. Data source
    var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };
    
    // 2.Query
    var evenNumbers = numbers.Where(it => it % 2 == 0);
    
    // 3.Query execution
    foreach (int item in evenNumbers)
    {
        Console.WriteLine(item);
    }
    var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };
    // เขียนแบบเต็ม
    var fullQry = from it in numbers
                  where it > 4
                  select it;
    
    // เขียนแบบย่อ
    var shortenQry = numbers.Where(it => it > 4);
    // เขียนแบบเต็ม
    var fullQry = from it in numbers
                  where it % 2 != 0 && it > 2
                  select it;
                  
    // เขียนแบบย่อ
    var shortenQry = numbers.Where(it => it % 2 != 0 && it > 3);
    var numbers = new int[] { 7, 5, 3, 1, 6, 2, 4 };
    // เขียนแบบเต็ม
    var fullQry = from it in numbers
                  orderby it
                  select it;
    
    // เขียนแบบย่อ
    var shortenQry = numbers.OrderBy(it => it);
    // เขียนแบบเต็ม
    var fullQry = from it in numbers
                  orderby it descending
                  select it;
    
    // เขียนแบบย่อ
    var shortenQry = numbers.OrderByDescending(it => it);
    var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };
    var qry = numbers.GroupBy(it => it % 2 == 0);
    var numbers = new int[] { 7, 5, 3, 1, 6, 2, 4 };
    // Forcing Immediate Execution
    var result = numbers.Max();
    var runner = 0;
    var qry = numbers.Where(it => it % 2 == 0 && runner++ > 0);
    Console.WriteLine(runner);
    var runner = 0;
    var qry = numbers.Where(it => it % 2 == 0 && runner++ > 0);
    Console.WriteLine($"Before Query Execution, Runner: {runner}");
    foreach (var item in qry)
    {
    }
    Console.WriteLine($"After Query Execution, Runner: {runner}");
    var runner = 0;
    var qry = numbers.Where(it => it % 2 == 0 && runner++ > 0).Count();
    Console.WriteLine($"Runner: {runner}");
    var numbers = Enumerable.Range(1, 100);
    // 1.กลุ่มเลขคู่
    var evenNumberQry = numbers
        .Where(it => it % 2 == 0);
    
    // 2.กลุ่มเลขคู่ที่ 5 หารลงตัว
    var eventWithDividableBy5NumberQry = evenNumberQry
        .Where(it => it % 5 == 0);
    
    // 3.กลุ่มเลขคู่ที่ 7 หารลงตัว
    var eventWithDividableBy7NumberQry = evenNumberQry
        .Where(it => it % 7 == 0);
    
    // 4.กลุ่มเลขคู่ที่ 5 และ 7 หารลงตัว
    var eventWithDividableBy5N7NumberQry = 
        eventWithDividableBy5NumberQry
        .Union(eventWithDividableBy7NumberQry);
    // 4.กลุ่มเลขคู่ที่ 5 และ 7 หารลงตัว
    var eventWithDividableBy5N7NumberQry = evenNumberQry
                    .Where(it => it % 5 == 0)
                    .Where(it => it % 7 == 0);

    ลองเขียน OOP ดูดิ๊

    🤔 เวลาเขาเอาหลัก Object-Oriented Programming มาใช้จริงๆมันเป็นยังไงนะ ?

    หลังจากที่เราได้เห็นหัวใจหลักของ OOP ไปเรียบร้อยแล้ว ดังนั้นในรอบนี้เราจะมาดูกันว่าเวลาที่ไปเจอโจทย์เราจะนำ OOP มาใช้แก้ปัญหาของเราได้ยังไง

    แนะนำให้อ่าน หัวใจหลักของ OOP มีทั้งหมด 4 อย่างคือ , , และ ถ้าสนใจอยากทบทวนเรื่องไหรก็จิ้มไปอ่านที่ชื่อมันได้เลย หรือจะไปดูจากเมนูด้านซ้ายมือก็ได้

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

    🧐 โจทย์ 01

    มีบริษัทมาจ้างเขียน เกมออนไลน์ ที่มีแค่ตัวละครเดียวคือ เด็กฝึกหัด (Novice) ซึ่งมันจะมี พลังชีวิต, พลังโจมตี, ประสบการณ์ที่จะใช้ในการอัพเลเวล และสามารถ เดิน, นั่ง, โจมตี และตายได้ ประมาณนี้ เราจะเขียนออกมายังไงดี ?

    🧒 แก้โจทย์

    ในการคิดแบบ OOP เราจะต้องแปลง ปัญหา ให้มาอยู่ในรูปแบบของ Model เสียก่อน โดยใช้หลักการของ Abstraction ดังนั้นเพื่อให้เข้าใจตรงกันผมกำลังจะสร้าง Model เพื่อใช้ในการเขียนโค้ด โดยมันจะต้องมี Properties และ Behaviors ต่างๆประมาณนี้

    ดังนั้นผมก็จะสร้าง Model ออกมา ซึ่งมีหน้าตาประมาณนี้

    มันก็ใช้งานได้แต่ใครอยากจะแก้พลังชีวิต พลังโจมตี หรืออะไรก็ตามในกลุ่มของ Properties ก็แก้ได้เลย ดังนั้นเราเลยต้องมาคิดในมุมของ Encapsulation ด้วย เช่น

    • ถ้าพลังชีวิตถูกลดลงมันจะต้องห้ามต่ำกว่า 0 และถ้าพลังชีวิตหมดก็แสดงว่าตัวละครจะต้องตาย

    • ถ้าได้รับค่าประสบการณ์เกิน 100 ให้ตัวละครเลเวลอัพได้เลย

    • พวก Properties ต่างๆต้องไม่ถูกแก้ได้มั่วซั่ว

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

    เลยเขียนโค้ดจัดการเรื่อง พลังชีวิต ก่อน

    ตามด้วยจัดการ ค่าประสบการณ์ ที่ทำให้เลเวลอัพได้ยังไง

    สุดท้าย Properties ต่างๆก็อย่าให้คนอื่นแก้ได้มั่วซั่ว และทำการกำหนดค่าเริ่มต้นซะ

    จาดโค้ดด้านบนก็จะเห็นแล้วว่า ของหลายๆอย่างเราซ่อนความวุ่นวายที่ภายนอกไม่จำเป็นต้องรู้ไว้ได้หมดแล้ว โดยการใช้ Encapsulation มาช่วย ซึ่งถ้าเราออกแบบ encapsulation ออกมาได้ดีมันก็จะช่วย เติมเต็ม ความเป็น Abstraction ได้มากขึ้น

    Abstraction + Encapsulation จะซ่อนความวุ่นวายทั้งหลายไว้ เหลือแค่เพียงความเรียบง่ายในการใช้งาน เพราะหลักการคือการสร้าง Component ที่มันรับผิดชอบตัวเองได้นั่นเอง

    ดังนั้นพอลองวาดภาพดูจะเห็นว่า Model ที่เราสร้างขึ้นมานั้น มันเป็น Component ที่รับผิดชอบเรื่องของตัวละครได้เบ็ดเสร็จในตัวมันเองเรียบร้อยเลย คนที่เอามันไปใช้ก็แค่เรียกใช้งานได้ถูก Properties / Methods เท่านั้น มันก็จะทำงานได้ถูกต้องโดยที่คนเรียกใช้ไม่ต้องรู้การทำงานภายในของมันเลยนั่นเอง

    🧐 โจทย์ 02

    เกมที่เราเขียนขายดีมาก เลยทำให้เราต้องเพิ่มตัวละครแบบอื่นๆเข้าไปบ้างนั่นคือ นักดาป (Swordman) และ พระ (Acolyte) เพื่อให้เกมมีความหลากหลายขึ้น และมีเงื่อนไขเพิ่มมาอีกนิสนุงนั่นคือ

    • นักดาป - จะมีพลังโจมตีเริ่มต้นที่ 10 หน่วย และ สามารถใช้ ท่าโจมตีพิเศษ ได้อีกด้วย

    • พระ - มีพลังโจมตีเริ่มต้นที่ 5 หน่วย และ สามารถใช้ การรักษาให้กับตัวเองได้ด้วย

    แล้วเราจะเขียนโปรแกรมยังไงดี เพื่อให้มีตัวละครใหม่ 2 ตัวเพิ่มขึ้นมา ในขณะที่ตัว เด็กฝึกหัด (Novice) ก็ยังต้องใช้งานได้เหมือนเดิมด้วยนะ

    🧒 แก้โจทย์

    จากที่ว่ามาเราก็จะกลับมาออกแบบโดยใช้หลัก Abstraction เพื่อตีโจทย์ก่อนว่า ปัญหา ที่เรากำลังเจอมันประกอบไปด้วยอะไรบ้างนั่นเอง ดังนั้นผมก็จะได้รูปประมาณนี้

    นักดาป (Swordman)

    พระ (Acolyte)

    เมื่อเราเห็นข้อมูลตัวอย่างของ นักดาป และ พระ เรียบร้อยแล้วเราน่าจะเห็นว่ามันมี Properties และ Behaviors ต่างๆที่คล้ายกับ เด็กฝึกหัด เลยใช่ไหม ซึ่งของที่มันมีเหมือนกันนั่นคือ

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

    อ่อ อย่าลืมนะว่าตัว Model กลางก็ต้องนำหลัก Abstraction + Encapsulation มาใช้ด้วยเช่นกัน ดังนั้น เราจะได้โค้ดจริงๆออกมาเป็นแบบนี้ (อย่าลืมเปลี่ยน private เป็น protected ด้วยนะ คลาสลูกจะได้เข้าถึงตัวแปรพวกนั้นได้)

    คราวนี้เราก็เอา Model เด็กฝึกหัด, นักดาป, พระ มาทำการต่อยอดความสามารถของ Character โดยใช้ความรู้จากเรื่อง Inheritance เข้ามาใช้นั่นเอง

    สุดท้ายมันก็จะเหลือแค่ของพิเศษที่มีเฉพาะตัวของ เด็กฝึกหัด นักดาป และ พระ เท่านั้น ซึ่งก็คือ

    • นักดาป - จะมีพลังโจมตีเริ่มต้นที่ 10 หน่วย และ สามารถใช้ ท่าโจมตีพิเศษ ได้อีกด้วย

    • พระ - มีพลังโจมตีเริ่มต้นที่ 5 หน่วย และ สามารถใช้ การรักษาให้กับตัวเองได้ด้วย

    ดังนั้นเราก็จะแก้ไข Model ของทั้ง 3 อาชีพให้กลายเป็นแบบนี้

    เพียงเท่านี้เราก็สามารถเลือกตัวละครเป็นตัวไหนก็ได้แล้ว โดยที่แต่ละตัวละครก็มีความสามารถที่ไม่เหมือนกันอีกด้วย ซึ่งโค้ดที่มีโครงสร้างแบบนี้ก็ง่ายที่จะเพิ่มตัวละครใหม่ๆเข้าไปได้แล้ว เพราะเราก็แค่ต่อยอด Model เดิมโดยการใช้ Inheritance เข้ามาช่วยนั่นเอง

    ลองเขียนแผนภาพเมื่อย้อนกลับมาดูความเป็น Component ของมันอีกทีซิ

    🧐 โจทย์ 03

    ตอนนี้คนเล่มถล่มทลายเลย แถมเขาอยากให้เล่นได้พร้อมกันหลายๆคนอีกด้วย ทำให้มีของที่เพิ่มเข้ามาในส่วนที่เรารับผิดชอบคือ

    • พระ - สามารถรักษาให้กับตัวเองและผู้เล่นคนอื่นได้ด้วย

    แล้วเราจะแก้ไขโค้ดเรายังไงให้รองรับความต้องการใหม่อันนี้ ?

    🧒 แก้โจทย์

    ในตอนนี้โจทย์ของเราอยู่ที่เมธอด Heal ของคลาส Acolyte ตามโค้ดด้านล่าง

    ปัญหาของมันคือ มันรักษาได้เฉพาะตัวเอง ดังนั้นถ้าเราอยากให้รักษาคนอื่นได้ เราก็ต้องส่ง parameter บางอย่างเข้าไปให้มันนั่นเอง แต่ว่าเราควรจะส่ง parameter อะไรเข้าไปดีล่ะ

    จากตรงนี้ผมขอเขียนแผนภาพ UML ก่อนละกัน จะได้เข้าใจภาพรวมทั้งหมดในตอนนี้ ซึ่งจะได้ออกมาตามรูปด้านล่าง

    จากรูปด้านบนจะเห็นว่า ถ้าเราใช้ Novice, Swordman หรือ Acolyte เข้าไปเป็น parameter นั่นหมายความว่าเราจะต้องมี method แบบนี้อย่างน้อย 3 ตัว

    มันก็ทำงานได้นะ แต่ถ้าในอนาคตมันมีตัวละครใหม่ๆล่ะ เราจะต้องไปเพิ่มเมธอดใหม่ทุกครั้งที่มีอาชีพใหม่เหรอ?

    ดังนั้นในจุดนี้เราจะใช้ความรู้เรื่อง Polymorphism เข้าช่วยแก้ปัญหา โดยการส่งคลาส Character เข้าไปเป็น parameter นั่นเอง เพราะ Base class สามารถทำงานกับ Sub class ได้ทุกตัวยังไงล่ะ ดังนั้นโค้ดเราก็จะออกมาเป็นแบบนี้

    เมื่อโค้ดเป็นแบบด้านบนแล้วนั่นหมายความว่า ทุกตัวละคร เราสามารถส่งไปทำการรักษาได้หมดเลย และต่อให้มีตัวละครใหม่ๆเข้ามาในอนาคต ที่เป็น sub class ของ Character มันก็จะทำงานกับ method นี้ได้ทันที นี่แหละตัวอย่าง ความยืดหยุ่น ที่ทำให้การเขียนโค้ดครั้งเดียว แต่ใช้ได้ตลอดไป

    🧐 โจทย์ 04

    ตอนนี้ผู้เล่นเริ่มบ่นอยากแต่งตัวละครสวยๆได้ ทางบริษัทเลยคิดว่า ทุกตัวละครสามารถใส่หมวกบนหัวได้ และ หมวกแต่ละแบบก็จะเพิ่มความสามารถต่างๆให้กับผู้เล่นได้ด้วย ดังนั้นหน้าที่นี้เลยตกมาที่เรา แล้วเราจะรับมือกับเรื่องนี้ยังไงดีกันนะ ?

    🧒 แก้โจทย์

    จำง่ายๆเวลาที่มีงานเข้ามาให้ดูก่อนว่างานนั้นเป็นงานที่ โค้ดเดิมทำได้อยู่แล้ว หรือ มันเป็นเรื่องใหม่

    • ถ้าโค้ดเดิมทำได้อยู่แล้ว เช่น เพิ่มตัวละครตัวใหม่เข้าไป อันนี้เราก็แค่ต่อยอด Model เดิมก็จะใช้งานได้ละ

    • ถ้ามันเป็นเรื่องใหม่ เช่นกรณีนี้ ให้เรานำหลักของ Abstraction กลับมาวิเคราะห์ดูเสมอนั่นเอง

    ดังนั้นในรอบนี้เราก็จะเอา Abstraction กลับมาช่วยตีโจทย์นั่นเอง โดยผมมองว่าไม่ว่าจะเป็นตัวละครตัวไหนก็ตาม ก็ควรที่จะใส่หมวกได้ และแม้แต่ตัวละครในอนาคตก็ควรจะต้องใส่หมวกได้เช่นกัน และหมวกก็ควรมีความหลากหลายด้วย ตามรูปเลย

    แล้วก็อย่าลืมมองกลับมาที่ Model เดิมที่เรามีด้วย ตามรูปด้านล่างเลย

    ซึ่งถ้าดูตามความเหมาะสมแล้ว เราควรจะเพิ่ม Property ของหมวกให้กับ Character นั่นเอง ดังนั้นเราลองคิดนิสนุงว่า Model หมวกที่สามารถเพิ่มสถานะให้กับผู้ใส่ได้ควรจะมีอะไรบ้าง (ผมแอบคิดมาละประมาณนี้ละกัน)

    นั่นก็หมายความว่าผมควรที่จะมี Model ของมันออกมาประมาณนี้

    ดังนั้นเราก็จะเอา Model ตัวใหม่อันนี้ไปใส่ไว้ใน Character ตามรูปด้านล่าง เพื่อให้ตัวละครรองรับการใส่หมวกได้ (ซึ่งบางตัวละครอาจจะไม่ใส่หมวกก็ได้นะ)

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

    ตัวอย่างโค้ดทั้งหมด

    🎯 บทสรุป

    น่าจะพอเห็นตัวอย่างการนำหลักของ Object-Oriented Programming ไปใช้ในการเขียนโค้ดกันชัดเจนมากยิ่งขึ้นแล้วนะ ซึ่งถ้าเรามองของต่างๆให้เป็น Component แล้วล่ะก็ เราจะสามารถเพิ่มลดความสามารถต่างๆเข้าไปในโปรแกรมได้ง่ายขึ้น เพราะตัวโค้ดเราแต่ละส่วนมันจะเหมือนกับ เลโก้ นั่นเอง ซึ่งมันจะช่วยทำให้เราเปลี่ยนชิ้นส่วนที่ไม่ต้องการ หรือ อยากให้มันมีการทำงานแบบอื่นๆก็สามารถทำได้ง่ายๆเลยนั่นเอง

    คำเตือน ในการออกแบบโดยใช้แนวคิดของ OOP ซึ่งมีหัวใจหลัก 4 ตัวนั้น ไม่ได้หมายความว่าเราจะต้องพยายามเอาหัวใจมันมาใช้งานทุกตัวนะ เพราะไม่อย่างนั้นเราจะทำให้ของมันยากขึ้นโดยใช่เหตุ ซึ่งถ้าใช้แค่หลักของ Abstraction เพื่อสร้าง Model แล้วทำงานได้หมด ก็ใช้แค่นั้นก็เพียงพอแล้วครับ

    คำเตือน ตามที่บอกไปว่าตัวอย่างทั้งหมดยังไม่ใช่การออกแบบที่ดีนัก ดังนั้นถ้าอยากรู้ว่าการออกแบบที่ดีเป็นยังไงแล้วล่ะก็ลองไปดูบทความถัดไป หรือกดจากลิงค์นี้ได้เลยครัช 👑 OOP + Power of Design****

    แนะนำให้อ่าน แผนภาพที่เอามาใช้ในการอธิบายในบทความนี้ชื่อว่า Class Diagram ซึ่งมันจะช่วยให้ Developer คุยกัน หรือ ทำความเข้าใจ หรือ ออกแบบได้ง่ายขึ้น เพราะเราสามารถมองภาพแล้วเข้าใจได้เลย ซึ่งถ้าเพื่อนๆสนใจศึกษาก็สามารถเข้าไปอ่านได้จากบทความด้านล่างนี้เลยครัช 👶 UML พื้นฐาน

    Abstraction
    Encapsulation
    Inheritance
    Polymorphism
    public class Hat
    {
        public string Description { get; set; }
        public int EffectOnHP { get; set; }
        public int EffectOnAttack { get; set; }
    }
    public class Character
    {
        private int hp;
        public int HP
        {
            get => hp;
            set
            {
                var hpTemp = hp;
                hp -= value;
                if (hp <= 0)
                {
                    hp = 0;
                    Status = "Dead";
                    Dead();
                }
                else
                {
                    hp = hpTemp;
                    Status = "Alive";
                }
            }
        }
    
        public int Atk { get; protected set; } = 10;
    
        private int exp;
        public int Exp
        {
            get => exp;
            set
            {
                var expTemp = exp;
                expTemp += value;
                if (expTemp >= 100)
                {
                    exp = 0;
                    Level++;
                }
                else
                {
                    exp = expTemp;
                }
            }
        }
        public int Level { get; protected set; }
        public string Status { get; protected set; } = "Alive";
    
        public Hat headEquipment { get; protected set; }
    
        public Character()
        {
            HP = 100;
        }
    
        public void Walk() { }
        public void Sit() { }
        public void Attack() { }
        public void Dead() { }
        public void EquipHead(Hat gear) { }
    }
    public class Novice : Character
    {
        public Novice()
        {
            Atk = 3;
        }
    }
    public class Swordman : Character
    {
        public Swordman()
        {
            Atk = 10;
        }
    
        public void SuperAttack() { }
    }
    public class Acolyte : Character
    {
        public Acolyte()
        {
            Atk = 5;
        }
    
        public void Heal(Character target) { }
    }
    public class Novice
    {
        public int HP { get; set; }
        public int Exp { get; set; }
        public int Atk { get; set; }
        public int Level { get; set; }
        public string Status { get; set; }
    
        public void Walk() { }
        public void Sit() { }
        public void Attack() { }
        public void Dead() { }
    }
    public class Novice
    {
        private int hp;
        public int HP
        {
            get => hp;
            set
            {
                var hpTemp = hp;
                hp -= value;
                if (hp <= 0)
                {
                    hp = 0;
                    Status = "Dead";
                    Dead();
                }
                else
                {
                    hp = hpTemp;
                    Status = "Alive";
                }
            }
        }
    
        ...
    }
    public class Novice
    {
        private int exp;
        public int Exp
        {
            get => exp;
            set
            {
                var expTemp = exp;
                expTemp += value;
                if (expTemp >= 100)
                {
                    exp = 0;
                    Level++;
                }
                else
                {
                    exp = expTemp;
                }
            }
        }
    
        ...
    }
    public class Novice
    {    
        public int Atk { get; private set; }
        public int Level { get; private set; }
        public string Status { get; private set; } = "Alive";
    
        public Novice()
        {
            HP = 100;
            Atk = 3;
        }
    
        ...
    }
    // Properties
    HP
    Exp
    Atk
    Level
    Status
    
    // Behaviors
    Walk()
    Sit()
    Attack()
    Dead()
    public class Character
    {
        public int HP { get; set; }
        public int Exp { get; set; }
        public int Atk { get; set; }
        public int Level { get; set; }
        public string Status { get; set; }
    
        public void Walk() { }
        public void Sit() { }
        public void Attack() { }
        public void Dead() { }
    }
    public class Character
    {
        private int hp;
        public int HP
        {
            get => hp;
            set
            {
                var hpTemp = hp;
                hp -= value;
                if (hp <= 0)
                {
                    hp = 0;
                    Status = "Dead";
                    Dead();
                }
                else
                {
                    hp = hpTemp;
                    Status = "Alive";
                }
            }
        }
    
        public int Atk { get; protected set; } = 3;
    
        private int exp;
        public int Exp
        {
            get => exp;
            set
            {
                var expTemp = exp;
                expTemp += value;
                if (expTemp >= 100)
                {
                    exp = 0;
                    Level++;
                }
                else
                {
                    exp = expTemp;
                }
            }
        }
        public int Level { get; protected set; }
        public string Status { get; protected set; } = "Alive";
    
        public Character()
        {
            HP = 100;
        }
    
        public void Walk() { }
        public void Sit() { }
        public void Attack() { }
        public void Dead() { }
    }
    public class Novice : Character
    {
    }
    
    public class Swordman : Character
    {
    }
    
    public class Acolyte : Character
    {
    }
    public class Novice : Character
    {
        public Novice()
        {
            Atk = 3;
        }
    }
    
    public class Swordman : Character
    {
        public Swordman()
        {
            Atk = 10;
        }
    
        public void SuperAttack() { }
    }
    
    public class Acolyte : Character
    {
        public Acolyte()
        {
            Atk = 5;
        }
    
        public void Heal() { }
    }
    public class Acolyte : Character
    {
        public void Heal() { }
    }
    public void Heal( ??? ) { }
    public void Heal(Novice target) { }
    public void Heal(Swordman target) { }
    public void Heal(Acolyte target) { }
    public void Heal(Character target) { }
    public class Hat
    {
        public string Description { get; set; }
        public int EffectOnHP { get; set; }
        public int EffectOnAttack { get; set; }
    }
    public class Character
    {
        public Hat headEquipment { get; protected set; }
    
        public void EquipHead(Hat gear) { }
    
        ...
    }

    🏗️ Builder Pattern

    แนวคิดในการรับมือกับ object ที่มีขั้นตอนการสร้างซับซ้อน

    เจ้าตัวนี้ผมขอตั้งชื่อเป็นภาษาไทยว่า ผู้สร้าง และมันอยู่ในกลุ่มของ 🤰 Creational Patterns ซึ่งเจ้าตัวนี้จะมาช่วยแก้ปัญหาเมื่อ เรามีคลาสที่มีขั้นตอนในการสร้างซับซ้อน และ การสร้าง object หลายๆอย่างที่มีขั้นตอนในการสร้างคล้ายๆกัน (อย่าพึ่งเสียเวลาอ่านตัวหนาพวกนั้นเลย) ไปดูโจทย์ของเรากันเลยดีกว่าจะได้เข้าใจได้เร็วขึ้น

    แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช 👦 Design Patterns

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

    🧐 โจทย์

    สมมุติว่าเกมของเรามี ระบบสร้างอาวุธ โดยอาวุธที่สร้างออกมานั้นมี 2 ประเภทคือ

    • อาวุธธรรมดา - อาวุธประเภทนี้จะ มีช่องว่าง 2 ช่อง ให้ใส่การ์ดเพิ่มความสามารถให้กับอาวุธได้

    • อาวุธธาตุ- อาวุธประเภทนี้จะ เลือกธาตุได้ เช่น ดิน น้ำ ลม ไฟ

    • อาวุธธรรมดา - จะต้องไม่มีธาตุ

    และอาวุธที่ถูกสร้างขึ้นมานั้นจะต้องถูก สลักชื่อคนสร้าง เอาไว้ในตัวอาวุธด้วย ตามรูปด้านล่างเบย

    นอกจากนี้ยังมีเรื่องของ ประเภทอาวุธ ด้วย ซึ่งอาวุธแต่ละประเภทก็จะมีลักษณะที่แตกต่างกัน เช่น

    • ดาป - เป็นการโจมตีทางกายภาพ และ ใช้ได้ในระยะประชิดเท่านั้น

    • คฑาเวทมนต์ - เป็นการโจมตีทางเวทมนต์ และ ใช้ได้ในระยะใกล้

    • ธนู - เป็นการโจมตีทางกายภาพ และ ใช้ได้ในระยะไกล

    และอย่างลืมว่าอาวุธพวกนี้จะต้องสามารถถูกสร้างโดยใส่ธาตุได้นะ ยกเว้นอาวุธพวกคฑาเวทมนต์ จะไม่สามารถใส่ธาตุได้ ตามรูปเลย

    แล้วเราจะเขียนโค้ดออกมายังไงดี เพื่อให้มันสามารถตอบโจทย์ความต้องการแบบนี้ ได้อย่างไม่มีปัญหากันนะ ?

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

    จากโจทย์ที่ได้มาเราก็ออกแบบ คลาสอาวุธ ของเรากันได้ประมาณนี้

    สุดท้ายเราก็ไปเขียนเมธอดที่ใช้ในการสร้างอาวุธออกมา โดยการโยน parameters ต่างๆเพื่อใช้ในการสร้างอาวุธ พร้อมกับเขียนเงื่อนไขต่างๆลงไป เช่น ถ้าเป็นอาวุธธาตุจะต้องไม่มีช่องใส่การ์ด บลาๆ ดังนั้นเราก็จะได้เมธอดออกมาหน้าตาประมาณนี้

    หากดูโค้ดตัวอย่างด้านล่างไม่รู้เรื่อง ให้กดที่ tab ข้างๆที่ชื่อว่า ตัวอย่างที่ 2 (แบบง่าย) ละกันนะ

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

    เพราะโค้ดของเรามันมีหลายอย่างที่ไม่ตรงหลักในการออกแบบที่ดี เช่น SRP, OCP ยังไงล่ะ

    Single-Responsibility Principle (SRP) การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ เวลาที่ Requirement เปลี่ยนมาทีนึง มันก็จมีโอกาสสูงมากที่การเปลี่ยนนั้นมันจะไปกระทบเจ้าสิ่งนั้น ทำให้เราต้องแก้ไขมัน ซึ่งผองเพื่อนอื่นๆที่มันดูแลอยู่นั้นไม่ได้เกี่ยวข้องเลยก็มีผลกระทบด้วยนั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย

    Open & Close Principle (OCP) การออกแบบที่ละเมิดหลักในการออกแบบนี้จะทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สำหรับใครที่ลืมหลักในการออกแบบเรื่องนี้ไปแล้วให้กดอ่านได้จากตรงนี้

    อาวล่ะเมื่อมาถึงตรงนี้ ผมขอแปลงโค้ดทั้งหมดของเราให้ไปเป็นภาพประมาณนี้ละกันจะได้เข้าใจกันได้ง่ายๆ แล้วเดี๋ยวเราลองไปวิเคราะห์ปัญหา แล้วค่อยๆแก้ปัญหากันทีละจุดกันดีกว่า

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

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

    จากปัญหาที่ว่ามาเราจะพบว่า ทุกครั้งที่มีอาวุธประเภทใหม่ๆ หรือ เงื่อนไขใหม่ๆ เข้ามามันจะทำให้ เราต้องไปแก้เจ้าเมธอด CreateNewWeapon เสมอ เลยทำให้ในอนาคตมันจะ บวมฉ่ำ อย่างไม่ต้องสงสัยเลย เพราะแค่ดูจากรูปก็เห็นแล้วว่ามันรับ parameters ยั้วเยี๊ยไปหมด ดังนั้นเงื่อนไขภายในก็น่าจะเยอะไม่แพ้กัน เมื่อมีการเปลี่ยนแปลงแก้ไขเกิดขึ้น มันก็จะนำปัญหามาหาเรายังไงล่ะ เราเรียกการบวมแบบนี้ว่า บวมออกข้าง

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

    และต่อให้เราแยกเมธอดเพื่อใช้ในการสร้างอาวุธมันก็ยังจะบวมออกเหมือนเดิมแต่เปลี่ยนเป็น บวมในแนวดิ่งแทน

    ส่วนหนึ่งในสาเหตุการบวมนั้นเกิดจาก มันรับผิดชอบหลายเรื่อง ยังไงล่ะ ดังนั้นเดี๋ยวเรามาแก้ปัญหาเรื่องนี้กันด้วยหลักในการออกแบบด้วย SRP กันเลย

    🔥 แก้ไขปัญหา

    จากที่อธิบายไปปัญหาหลักของเราคือ ของหลายๆอย่างมันพันกันอยู่ภายในเมธอดเดียว ดังนั้นเราจะต้องค่อยๆ แก้ปมมันทีละเรื่องตามด้านล่างนี้เบย

    หน้าที่ในการสร้างอาวุธ

    เราไม่ควรจะรวมไว้ในเมธอดเดียว แต่มันควรจะแยกการสร้างอาวุธออกเป็นอาวุธแต่ละประเภทนั่นเอง เช่น ตัวสร้างดาป ตัวสร้างคฑาเวทมนต์ ตัวสร้างธนู ตามรูปด้านล่าง

    จัดการความซับซ้อนของการสร้าง

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

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

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

    ซึ่งจากโครงสร้างใหม่ด้านบน เมื่อเราอยากจะกำหนดอะไรเราก็แค่เรียกใช้เมธอดพวกนั้นในการกำหนดค่าต่างๆก็พอ เช่น อยากสร้างดาปธาตุไฟ ก็จะเขียนออกมาราวๆนี้

    จากโค้ดด้านบนจะเห็นว่าจะเพิ่มเติมอะไรซักที มันจะเขียนค่อนข้างยาว ดังนั้นเราเลยแก้ไขให้มันสามารถ เชื่อมคำสั่ง (Chain) กันได้นั่นเอง โดยการแก้ return type ให้มันส่ง object ตัวมันเองกลับมา ตามรูปเรย

    ดังนั้นโค้ดเวลาที่เราเรียกใช้มันก็จะเป็นแบบนี้

    ซึ่งเจ้าคลาสตระกูล Maker จะต้องทำหน้าที่รับผิดชอบในการสร้างอาวุธให้เรานั่นเอง และของบางอย่างเราก็อาจจะไม่ต้องกำหนดค่าให้มันก็ได้ เช่น SwordMaker ก็จะรู้ว่าตัวเองนั้นจะต้องกำหนดพวก รูปแบบการโจมตี ประเภทของอาวุธ และ ระยะการโจมตีเป็นเท่าไหร่นั่นเอง และ มันก็ควรจะมีเมธอดที่สั่งให้มันสร้างอาวุธออกมาให้เราได้ด้วยนั่นเอง ตามนี้เบย

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

    ส่วนโค้ดของคลาส SwordMaker ก็จะเป็นแบบนี้นั่นเอง

    จากที่ว่าไปทั้งหมดเราก็จะสามารถสร้าง interface กลางที่ใช้ในการสร้างอาวุธต่างๆได้ออกมาราวๆนี้

    ซึ่งจากที่ทำมาทั้งหมด เราก็จะได้สิ่งที่เรียกว่า Builder แล้วนั่นเองครัช โดยเจ้าคลาส Builder นั้นมีหน้าที่ในการช่วยให้การสร้าง object ต่างทำได้ง่ายขึ้น โดยที่ Builder แต่ละตัวก็จะต้องรู้เงื่อนไข ข้อจำกัด หรือกฎต่างๆที่ตัวมันเองต้องรับผิดชอบด้วย เลยทำให้เรามี builder ในการสร้างของแตกต่างกันได้หลายๆแบบนั่นเอง (ลองไปดูโค้ดจริงๆที่ด้านล่างสุดได้นะ ว่า builder มันรับผิดชอบเงื่อนไขของมันต่างกันยังไง)

    🤔 มันใช้งานยากไปหน่อยไหม ?

    แม้ว่าเราจะทำให้โค้ดเราเรียกใช้งานได้ง่ายขึ้นเยอะเมื่อเทียบกับโค้ดแรกเริ่มของเราละ แต่ถ้าทุกครั้งที่เราต้องการสร้างดาป เราก็ต้องเขียนโค้ดแบบด้านล่างนี้เสมอ มันจะทำให้โปรแกรมของเรามีโค้ดซ้ำกันเต็มไปหมดเลยเหรอ ? แบบนี้ก็ขัดกับหลักในการออกแบบเรื่อง Don't Repeat Yourself (DRY) อะดิ ??

    ดังนั้นเพื่อแก้ปัญหาของพวกนี้ มันเลยมีอีกตัวนึงที่จะมาช่วยให้เราทำงานกับ Builder ได้ง่ายขึ้น นั่นก็คือสิ่งที่เรียกว่า Director นั่นเอง

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

    ซึ่งเจ้า Director จะรู้ว่าถ้าเราอยากจะได้อาวุธธรรมดา หรือ อาวุธธาตุ มันจะต้องสั่งงาน Builder ให้ทำงานยังไงถึงจะได้ของที่เราต้องการกลับมาเสมอนั่นเอง

    ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า Builder Pattern เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏

    🤔 Builder คือไย ?

    🔥 เป้าหมายในการแก้ปัญหา

    • ช่วยให้เรา สร้าง object ที่มีขั้นตอนในการสร้างที่ซับซ้อน ให้ถูกสร้างได้ง่ายๆ

    • ช่วยให้ โค้ดขั้นตอนการสร้างที่เหมือนๆกันรวมอยู่ที่เดียวกัน แต่สามารถสร้าง object ที่แตกต่างกันได้

    🔥 วิธีการใช้

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

    เวลาที่เราอยากสร้างอะไรก็ตาม เจ้าสิ่งนั้นเราจะเรียกมันว่า Product ซึ่งเจ้าตัวนี้แนะนำว่าให้มันเป็น interface ได้ก็จะดีมาก

    ส่วนการสร้าง product นั้นมันอาจจะมี options ให้เลือกจุกจิกมากมาย ดังนั้นเราก็จะทำเป็น interface กลางในการสร้างเอาไว้ซะ โดยเราจะเรียกมันว่า Builder และพวก options ต่างๆที่เราสามารถเพิ่มเข้าไปได้เราก็จะแปลงให้มันเป็นเมธอดต่างๆเพื่อให้เราสามารถเพิ่มลดได้นั่นเอง และก็อย่าลืมให้มันมีมีธอดในการสร้าง product จริงๆด้วยนะตามรูปเลย

    เจ้าตัวที่จะสร้าง product ที่แท้จริง ก็จะมา implement IBuilder ต่ออีกทีนึง และมันก็จะรู้เงื่อนไข กฎต่างๆในการสร้าง product ที่มันรับผิดชอบอยู่นั่นเอง ซึ่งเราเรียกมันว่า ConcreteBuilder ซึ่งถ้าเรามี product หลายๆแบบ ก็จะมี ConcreteBuilder หลายๆตัวนั่นเอง ตามรูปด้านล่าง

    สุดท้ายขั้นตอนในการสร้างที่เหมือนๆกัน เราก็จะให้มันไปอยู่กับสิ่งที่เรียกว่า Director นั่นเอง ซึ่งเราอาจจะมีการสร้างหลายๆแบบไว้กับ Director ก็ได้

    ดังนั้นภาพรวมทั้งหมดของ Builder Pattern เลยออกมาเป็นประมาณนี้

    ไหนลองเอาที่เราออกแบบมาเทียบกันดูดิ๊ ... เหมือนกันเปี๊ยบเบย

    ข้อแนะนำ ของทุกอย่างใน Design Patterns ทุกตัว เราไม่จำเป็นต้องทำตาม หรือ มีครบเหมือนตามที่เขาบอกไว้ก็ได้ (ถ้าเข้าใจ + มีเหตุผลที่ดีพอ) เพราะสิ่งที่ Pattern แต่ละตัวต้องการจะบอกเราคือ แนวทาง และเหตุผลในการออกแบบเพียงเท่านั้น ซึ่งสิ่งที่เราต้องทำต่อก็คือนำมันไปประยุกต์ให้เข้ากับปัญหาที่เราเจออยู่ให้เหมาะสมนั่นเอง

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

    🥰 ลดการบวม

    จากปัญหาด้านบนที่เวลามีของใหม่ๆเพิ่มเข้ามามันจะทำให้คลาสของเราบวมออก ไม่บวมออกข้าง ก็ออกบวมออกในแนวดิ่ง แถม parameters ที่ส่งไปบางทีก็ไม่จำเป็นต้องส่งไปก็ได้สำหรับางการณี ก็น่าจะพอเห็นข้อดีของการใช้ Builder เพียงอย่างเดียวกันแล้วนะ เพราะมันทำให้เราปรับแต่งเพิ่มลดได้ตามใจชอบเลย

    🥰 ลดความซ้ำซ้อน

    จะเห็นว่าขั้นตอนในการสร้างอาวุธไม่ว่าจะเป็นแบบ อาวุธธรรมดา หรือ อาวุธธาตุ มันก็มีขั้นตอนในการสร้างที่ตายตัวในรูปแบบของมัน ดังนั้นแทนที่เราจะต้องไปเขียนโค้ดเดิมซ้ำๆ เราก็สามารถนำโค้ดพวกนั้นไปไว้ใน director เพื่อลดการเขียนโค้ดซ้ำได้แล้ว แถมโค้ดตัวนั้นยังสามารถผลิต product ที่มีขั้นตอนการสร้างแบบเดียวกันได้อีกไม่จำกัดรูปแบบเลย เพียงแค่ส่ง Builder style ที่ต้องการเข้ามานั่นเอง

    หมายเหตุ การใช้ Builder Pattern นั้นจริงๆมีอีกหลายเรื่องเลยที่เป็นข้อดี เช่น

    • ลด Hardcode ในการสร้าง object

    • ลดปัญหาเรื่อง Dependencies ต่างๆ

    📝 ตัวอย่างโค้ดทั้งหมด

    ผลลัพท์ Saladpuk (-) Katana sword [2] slots, Atk: 10, MAtk: 0, Range: 1 Saladpuk (Fire) Katana sword [0] slots, Atk: 10, MAtk: 0, Range: 1 Saladpuk (-) Soul staff [2] slots, Atk: 0, MAtk: 10, Range: 5 Saladpuk (-) Soul staff [2] slots, Atk: 0, MAtk: 10, Range: 5 Saladpuk (-) Hunter bow [2] slots, Atk: 10, MAtk: 0, Range: 12 Saladpuk (Wind) Hunter bow [0] slots, Atk: 10, MAtk: 0, Range: 12

    🎯 บทสรุป

    👍 ข้อดี

    การนำ Builder Pattern มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง ทำให้เราสามารถเปลี่ยนแปลง แก้ไข รองรับสิ่งต่างๆได้มากขึ้น แถมยังช่วยลดโค้ดการสร้าง object ที่มีขั้นตอนในการสร้างเหมือนๆกันได้อีกด้วย

    👎 ข้อเสีย

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

    🤙 ทางเลือก

    เราสามารถนำ Framework พวก Dependency Injection (DI) เข้ามาใช้แทนได้นะจ๊ะ โค้ดกระชับหลับสบายเต็มตื่นด้วย

    ข้อควรระวัง อย่านำ Builder Pattern ไปใช้มั่วซั่ว เพราะมันทำให้โค้ดของเราซับซ้อนขึ้นเยอะเลยแทนที่เราจะใช้คำสั่ง new แบบปรกติ ดังนั้นให้ชั่งน้ำหนักให้ดีเสียก่อนว่าปัญหาที่เราเจออยู่นั้น มันวุ่นวาย เทสยาก โค้ดมันผูกกันอยู่เยอะหรือเปล่า ถ้าชั่งน้ำหนักแล้ว + มีเหตุผลที่เพียงพอที่จะใช้ก็จงใช้ให้สบายใจไปเถิด

    เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

    อาวุธธาตุ- จะต้องไม่มีช่องว่างสำหรับใส่การ์ด

    ลดปัญหาเรื่องการเทส

    ซึ่งผมเขียนอธิบายไว้ใน Factory Pattern น่าจะละเอียดพออยู่แล้ว ดังนั้นลองไปอ่านกันดูได้จากลิงค์นี้เบยครัช Factory Pattern ทำไมต้องใช้ด้วย ?

    Single-Responsibility Principle
    Open & Close Principle
    Mr.Saladpuk
    บวมออกข้าง
    ย้ายที่บวม
    บวมแนวดิ่ง
    ขอทำแค่ดาปให้ดูอันเดียวนะ ไม่งั้นรูปจะรกมาก
    ตัวที่ขีดเส้นใต้สีแดงคือสิ่งที่แก้ไขไปนะ
    public Weapon CreateNewWeapon(string name, 
        string element, 
        string creatorName, 
        bool isPhysical, 
        string type,
        int range)
    {
        const int DefaultAttack = 10;
        const int NoneElementSlots = 2;
        var isElementWeapon = !string.IsNullOrWhiteSpace(element);
        var weapon = new Weapon
        {
            Name = name,
            CreatorName = creatorName,
            Element = isElementWeapon ? element : string.Empty,
            MaximumSlots = isElementWeapon ? 0 : NoneElementSlots,
            PhysicalAttack = isPhysical ? DefaultAttack : 0,
            MagicalAttack = isPhysical ? 0 : DefaultAttack,
            WeaponType = type,
            MinimumAttackRange = range,
        };
        var isStaff = type == "staff";
        weapon.Element = isStaff ? string.Empty : weapon.Element;
        weapon.MaximumSlots = isStaff ? NoneElementSlots : weapon.MaximumSlots;
        return weapon;
    }
    public Weapon CreateNewWeapon(string name,
        string element,
        string creatorName,
        bool isPhysical,
        string type,
        int range)
    {
        const int DefaultAttack = 10;
        const int NoneElementSlots = 2;
        var weapon = new Weapon
        {
            Name = name,
            CreatorName = creatorName,
            WeaponType = type,
            MinimumAttackRange = range,
        };
    
        var isElementWeapon = !string.IsNullOrWhiteSpace(element);
        var isStaff = type == "staff";
        if (isElementWeapon && !isStaff)
        {
            weapon.Element = element;
        }
        else
        {
            weapon.Element = string.Empty;
            weapon.MaximumSlots = NoneElementSlots ;
        }
    
        if (isPhysical)
        {
            weapon.PhysicalAttack = DefaultAttack;
        }
        else
        {
            weapon.MagicalAttack = DefaultAttack;
        }
    
        return weapon;
    }
    public Weapon CreateWeapon(string name,
        string element,
        string creatorName,
        bool isPhysical,
        string type,
        int range,
        DateTime? currentTime)
    { ... }
    var weapon = CreateWeapon("a", "", true, "sword", 1, null);
    var swordMaker = new SwordMaker();
    swordMaker.SetName("Katana");
    swordMaker.SetElement("Fire");
    swordMaker.SetCreatorName("Saladpuk");
    swordMaker.SetAttackType(true);
    swordMaker.SetType("Sword");
    swordMaker.SetAttackRange(1);
    var swordMaker = new SwordMaker();
    swordMaker
        .SetName("Katana")
        .SetElement("Fire")
        .SetCreatorName("Saladpuk")
        .SetAttackType(true)
        .SetType("Sword")
        .SetAttackRange(1);
    var fireSword = new SwordMaker()
        .SetName("Katana")
        .SetElement("Fire")
        .SetCreatorName("Saladpuk")
        .GetWeapon();
    public class SwordMaker
    {
        private int cardSlots = 2;
        private string productName;
        private string elementType;
        private string creatorName;
    
        public SwordMaker SetName(string name)
        {
            productName = name;
            return this;
        }
    
        public SwordMaker SetElement(string element)
        {
            cardSlots = 0;
            elementType = element;
            return this;
        }
    
        public SwordMaker SetCreatorName(string name)
        {
            creatorName = name;
            return this;
        }
    
        public Weapon GetWeapon()
        {
            return new Weapon
            {
                Name = productName,
                Element = elementType,
                CreatorName = creatorName,
                PhysicalAttack = 10,
                MaximumSlots = cardSlots,
                MinimumAttackRange = 1,
                WeaponType = "sword"
            };
        }
    }
    var fireSword = new SwordMaker()
        .SetName("Katana")
        .SetElement("Fire")
        .SetCreatorName("Saladpuk")
        .GetWeapon();
    public class WeaponDirector
    {
        public Weapon CreateBasicWeapon(IWeaponMaker maker)
        {
            return maker
                    .SetCreatorName("Saladpuk")
                    .GetWeapon();
        }
    
        public Weapon CreateFireWeapon(IWeaponMaker maker)
        {
            return maker
                    .SetElement("Fire")
                    .SetCreatorName("Saladpuk")
                    .GetWeapon();
        }
    
        // ขอเขียนแค่นี้นะ บทความยาวม๊วกละ
    }
    static void Main(string[] args)
    {
        var director = new WeaponDirector();
    
        var swordMaker = new SwordMaker();
        var basicSword = director.CreateBasicWeapon(swordMaker);
        var earthSword = director.CreateFireWeapon(swordMaker);
        Console.WriteLine(basicSword);
        Console.WriteLine(earthSword);
    
        var staffMaker = new StaffMaker();
        var basicStaff = director.CreateBasicWeapon(staffMaker);
        var waterStaff = director.CreateWaterWeapon(staffMaker);
        Console.WriteLine(basicStaff);
        Console.WriteLine(waterStaff);
    
        var bowMaker = new BowMaker();
        var basicBow = director.CreateBasicWeapon(bowMaker);
        var windBow = director.CreateWindWeapon(bowMaker);
        Console.WriteLine(basicBow);
        Console.WriteLine(windBow);
    }
    public class Weapon
    {
        public string Name { get; set; }
        public string Element { get; set; }
        public string CreatorName { get; set; }
        public int MaximumSlots { get; set; }
        public int PhysicalAttack { get; set; }
        public int MagicalAttack { get; set; }
        public string WeaponType { get; set; }
        public int MinimumAttackRange { get; set; }
    
        public override string ToString()
        {
            var builder = new System.Text.StringBuilder()
                .Append($"{CreatorName} ({Element}) {Name} ")
                .Append($"{WeaponType} [{MaximumSlots}] slots, ")
                .Append($"Atk: {PhysicalAttack}, ")
                .Append($"MAtk: {MagicalAttack}, ")
                .Append($"Range: {MinimumAttackRange}");
            return builder.ToString();
        }
    }
    public interface IWeaponMaker
    {
        IWeaponMaker SetElement(string elementType);
        IWeaponMaker SetCreatorName(string name);
        Weapon GetWeapon();
    }
    public class SwordMaker : IWeaponMaker
    {
        private int cardSlots = 2;
        private string elementType;
        private string creatorName;
    
        public IWeaponMaker SetElement(string element)
        {
            cardSlots = 0;
            elementType = element;
            return this;
        }
    
        public IWeaponMaker SetCreatorName(string name)
        {
            creatorName = name;
            return this;
        }
    
        public Weapon GetWeapon()
        {
            return new Weapon
            {
                Name = "Katana",
                Element = elementType ?? "-",
                CreatorName = creatorName,
                PhysicalAttack = 10,
                MaximumSlots = cardSlots,
                MinimumAttackRange = 1,
                WeaponType = "sword"
            };
        }
    }
    public class StaffMaker : IWeaponMaker
    {
        private string creatorName;
    
        public IWeaponMaker SetElement(string element)
        {
            return this;
        }
    
        public IWeaponMaker SetCreatorName(string name)
        {
            creatorName = name;
            return this;
        }
    
        public Weapon GetWeapon()
        {
            return new Weapon
            {
                Name = "Soul",
                Element = "-",
                CreatorName = creatorName,
                MagicalAttack = 10,
                MaximumSlots = 2,
                MinimumAttackRange = 5,
                WeaponType = "staff"
            };
        }
    }
    public class BowMaker : IWeaponMaker
    {
        private int cardSlots = 2;
        private string elementType;
        private string creatorName;
    
        public IWeaponMaker SetElement(string element)
        {
            cardSlots = 0;
            elementType = element;
            return this;
        }
    
        public IWeaponMaker SetCreatorName(string name)
        {
            creatorName = name;
            return this;
        }
    
        public Weapon GetWeapon()
        {
            return new Weapon
            {
                Name = "Hunter",
                Element = elementType ?? "-",
                CreatorName = creatorName,
                PhysicalAttack = 10,
                MaximumSlots = cardSlots,
                MinimumAttackRange = 12,
                WeaponType = "bow"
            };
        }
    }
    public class WeaponDirector
    {
        public Weapon CreateBasicWeapon(IWeaponMaker maker)
        {
            var product = maker
                .SetCreatorName("Saladpuk")
                .GetWeapon();
            return product;
        }
    
        public Weapon CreateFireWeapon(IWeaponMaker maker)
            => createElementWeapon(maker, "Fire");
    
        public Weapon CreateWindWeapon(IWeaponMaker maker)
            => createElementWeapon(maker, "Wind");
    
        public Weapon CreateWaterWeapon(IWeaponMaker maker)
            => createElementWeapon(maker, "Water");
    
        public Weapon CreateEarthWeapon(IWeaponMaker maker)
            => createElementWeapon(maker, "Earth");
    
        private Weapon createElementWeapon(IWeaponMaker maker, string element)
        {
            var product = maker
                .SetElement(element)
                .SetCreatorName("Saladpuk")
                .GetWeapon();
            return product;
        }
    }

    พระคัมภีร์การใช้คำสั่ง LINQ

    🤔 คำสั่งของ LINQ ที่ได้ใช้บ่อยๆมีไรบ้างนะ

    บทความนี้เป็นบทความที่แยกออกมาจากเรื่อง LINQ ซึ่งเป็นหนึ่งในคำสั่งเทพเจ้าของสาย .NET ซึ่งมันจะทำให้ developer ทำงานได้สบายลงแบบฝุดๆ ดังนั้นใครยังไม่รู้เรื่อง LINQ ให้กลับไปอ่านบทความนี้ก่อนเน่อ Saladpuk - LINQ 101

    Filtering Data

    Where - เป็นการเลือกเอาเฉพาะข้อมูลที่เราสนใจออกมา เช่น มี data source เป็นเลข 1~100 แล้วต้องการเอาเฉพาะเลขที่ 5 และ 7 หารลงตัวออกมา ก็จะเขียนออกมาได้เป็นแบบนี้

    Projection Operations

    Select - เป็นการเลือกว่า data source ที่เราไปดึงข้อมูลมา เราจะดัดแปลงแก้ไข หรือ เลือกเอาเฉพาะข้อมูลบางส่วนออกมาใช้

    เช่นมี collection เป็นเลข 1~5 ตอนที่เราจะเอามาทำงานด้วยเราจะแก้ให้มันถูก คูณด้วย 10 ก่อนค่อยเอามาใช้งาน ก็จะเขียนได้แบบนี้

    หรือ จะให้มันเปลี่ยนเป็นข้อมูลอีกประเภทนึงเลยก็ได้

    ส่วนถ้าข้อมูลใน data source มันวุ่นวายเกินไป เราก็สามารถเลือกแค่บางส่วนของมันมาใช้ก็ได้นะ เช่น เราอยากได้แค่ Name ที่อยู่ใน collection มาใช้เท่านั้น ก็เขียนเป็นแบบนี้ได้

    SelectMany - เป็นการเลือกเข้าไปถึงหน่วยย่อยของ collection ที่ซ้อนภายใน collection อีกทีนึง

    แนะนำให้อ่าน คำสั่ง SelectMany สำหรับคนที่พึ่งหัดใช้ LINQ อาจจะ งงๆ หน่อยแต่ถ้าเราได้ทำงานร่วมกับพวก collection ซ้อน collection แล้วล่ะก็ควรจะทำความเข้าใจมันเอาไว้นะ ซึ่งอ่านได้จากลิงค์นี้เลย

    Element Operations

    ในบางทีเราอยากจะทำงานกับข้อมูลแค่ตัวใดตัวหนึ่งหรือส่วนหนึ่งที่อยู่ใน collection เราก็สามารถใช้คำสั่งที่อยู่ด้านล่างได้ เช่น เรามี data source ที่มีข้อมูลเป็นเลข 1~100

    First - เอาเฉพาะตัวแรกออกมา

    FirstOrDefault - เหมือนกับ First ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา

    Last - เอาเฉพาะตัวสุดท้ายออกมา

    LastOrDefault - เหมือนกับ Last ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา

    ElementAt - เป็นการดึงค่าที่อยู่ใน index ที่กำหนดออกมา

    ElementAtOrDefault - เหมือนกับ ElementAt ทุกประการ ต่างกันแค่ถ้ามันดึงค่าออกมาไม่ได้มันจะส่งค่า default ของ data type นั้นๆกลับมา

    อันตราย ถ้า data source เป็น collection ว่าง แล้วไปใช้คำสั่งพวก First, Last, ElementAt มันจะทำให้เกิด Exception ได้ครับ ดังนั้นโดยปรกติผมจะแนะนำให้ใช้คำสั่ง FirstOrDefault, LastOrDefault, ElementAtOrDefault แทนมากกว่า เพราะค่า overhead ในการจัดการกับ error มันสูงกว่าครับ

    Partitioning Data

    เวลาที่เราทำงานกับ data source ปริมาณมากๆ เราสามารถที่จะทำการแบ่งข้อมูลออกเป็นส่วนๆ เพื่อให้ง่ายในการทำงานได้ เช่น มี collection ตัวเลข 1~100 อยู่ตามด้านล่าง

    Take - เป็นการสั่งให้ดึงข้อมูลจาก data source ออกมาเท่าที่เรากำหนดไว้ เช่น เราอยากดึงข้อมูลมาแค่ 5 ตัวแรกก่อน เราก็จะเขียนได้ว่า

    TakeLast - เหมือนกับ Take แต่จะดึงมาจากด้านหลังสุด เช่น อยากจะดึงข้อมูล 5 ตัวจากด้านหลังสุดออกมา

    TakeWhile - เป็นการสั่งให้มันดึงข้อมูลจาก data source ออกมาเรื่อยๆจนกว่าจะเจอตัวแรกที่ทำให้เงื่อนไขไม่เป็นจริง เช่น ให้ดึงมาเรื่อยๆถ้าเลขที่ดึงมามันยังน้อยกว่า 8

    Skip - สั่งให้ข้ามข้อมูลเท่ากับที่เรากำหนด เช่น เราต้องการข้ามข้อมูล 4 ตัวแรกไป

    SkipLast - เหมือนกับ Skip ต่างกันแค่มันจะข้ามเฉพาะตัวด้านหลังสุด เช่น อยากจะข้ามข้อมูล 4 ตัวสุดท้ายไป

    SkipWhile - เป็นการสั่งให้มันข้ามข้อมูลไปเรื่อยๆ ถ้าเงื่อนไขยังเป็นจริงอยู่ และจะหยุดข้ามเมื่อเจอข้อมูลตัวแรกที่ไม่ตรงเงื่อนไข เช่น อยากจะข้ามไปเรื่อยๆจนกว่าจะเจอตัวแรกที่มากกว่า 50

    Set Operations

    เราสามารถทำงานกับ data source ที่เป็น 2 กลุ่มให้มาทำงานร่วมกันได้ 3 แบบคือ

    เช่นเรามีข้อมูลกลุ่ม a กับกลุ่ม b เป็นแบบนี้

    Intersect - ตามรูปเลยคือ เอาเฉพาะที่มันเหมือนกันออกมา

    Union - ตามรูปเลยคือ เอาทั้งสองกลุ่มมารวมกัน

    Except - ตามรูปเลยคือ เอาเฉพาะของที่ไม่ซ้ำกับอีกกลุ่มออกมา

    Distinct - เป็นการตัดตัวซ้ำทิ้ง

    Sorting Data

    การเรียงลำดับเราทำได้ 3 แบบ น้อยไปมาก มากไปน้อย และ กลับด้านข้อมูล เช่นเรามี data source เป็นแบบนี้

    OrderBy - เรียงลำดับจากน้อยไปมาก หรือถ้าเป็นตัวอักษรจะเป็นการเรียงจาก a~Z

    OrderByDescending - เรียงลำดับจากมากไปน้อย

    Reverse - เรียงลำดับแบบกลับด้าน ขวาไปซ้าย แทน

    ThenBy และ ThenByDescending - แต่ถ้าข้อมูลมีความซับซ้อนมากขึ้น เราสามารถกำหนดความสำคัญในการเรียงลำดับได้ด้วย เช่น เรียงลำดับจากคะแนนน้อยไปมาก แต่ถ้าคะแนนเท่ากันให้เรียงจากชื่อตามลำดับตัวอักษรก็จะเขียนแบบนี้ได้

    Quantifier Operations

    เราสามารถหาผลลัพท์จากข้อมูลใน collection ได้เช่น มีบางตัวไหม? หรือ ทุกตัวเป็นแบบนี้ไหม? อาจจะฟังแล้ว งงๆ ไปดูตัวอย่างเลยดีกว่า โดยสมมุติว่าผมมี data source เป็นแบบนี้

    Any - ถามว่ามีซักตัวไหมที่เป็นแบบนี้ เช่น อยากรู้ว่ามีซักตัวไหมใน collection ที่มีค่ามากกว่า 9 ก็สามารถเขียนเป็น

    All - ถามว่าทุกตัวเป็นแบบนี้หรือเปล่า เช่น อยากเช็คว่าทุกตัวใน collection มากกว่า 5 หรือเปล่า

    Contains - ถามว่าภายในนั้นมีตัวนี้อยู่หรือเปล่า เช่น collection นั้นมีเลข 8 อยู่ในนั้นหรือเปล่า

    Grouping Data

    GroupBy - สั่งให้มันจัดกลุ่มของข้อมูลได้ เช่น มี collection ของคนหลายๆคน แล้วเราอยากให้จัดกลุ่มคนตามอายุ เราก็จะเขียนได้ว่า

    Generation Operations

    ถ้าเราต้องการสร้างข้อมูลที่เป็น collection ขึ้นมาแบบง่ายๆ เราก็สามารถใช้ LINQ ช่วยสร้างได้

    Range - สร้างชุดตัวเลขออกมา เช่น อยากได้ collection ตัวเลขตั้งแต่ 1 ถึง 100

    Empty - สร้าง collection ว่างออกมา เช่น เราอยากได้ collection ของตัวเลข แต่ไม่ต้องมีข้อมูลอะไรอยู่ข้างในนะ

    DefaultIfEmpty - ถ้าเราต้องไปทำงานกับ collection ตัวเลขซักตัว แต่ถ้า collection นั้นมันเป็นค่าว่าง เราจะกำหนดค่า 9 ให้มันไปใช้แทน

    Repeat - สร้างชุดข้อมูลซ้ำๆกันออกมา เช่น อยากได้ collection เลข 5 ซ้ำกัน 3 ตัว ก็เขียนแบบนี้ได้

    Converting Data Types

    เราสามารถแปลง data source ของเราจาก data type นึงไปยังอีก data type นึงก็ได้นะ เช่น มีข้อมูล collection เลข 1~5 ตามนี้

    AsEnumerable - แปลงให้มันกลับมาเป็น IEnumerable<T> เอาไว้ช่วยแปลงจาก collection อะไรก็ตามให้กลับมาสู่ base class ของกลุ่ม collection

    AsQueryable - แปลงให้คำสั่งทั้งหมดยังเป็นแค่ Query เท่านั้น ซึ่งใช้ได้ดีตอนที่ทำงานร่วมกับ database เพราะเราจะได้ส่งแต่คำสั่งไปประมวลผลที่ database เท่านั้นไม่ได้ส่งข้อมูลปริมาณมหาศาลกลับมาถล่มที่ client

    Cast - แปลงข้อมูลจาก data source ให้กลายเป็น data type ที่กำหนด

    OfType - เลือกเอาเฉพาะ data type ที่ตรงกับที่กำหนด

    ToArray - แปลงให้ collection นั้นๆกลายเป็น Array

    ToList - แปลงให้ collection นั้นๆกลายเป็น List

    ToDictionary - แปลงให้ collection นั้นๆกลายเป็น Dictionary<K, V> เช่นทำการจัดกลุ่มว่าใครเรียนอยู่ห้องไหนบ้าง แล้วทำการไปสร้างเป็น dictionary

    Concatenation Operations

    Concate - เป็นการเอา 2 collection มาต่อกันแบบดื้อๆเลย

    Aggregation Operations

    เป็นกลุ่มคำสั่งที่ได้ผลลัพท์กลับมาเลย และเป็นการทำงานแบบ Imperative เช่นมี data source เป็นเลข 1~10 ตามนี้

    Sum - หาผลรวม

    Average - หาค่าเฉลี่ย

    Max - หาค่าสูงสุด

    Min - หาค่าต่ำสุด

    Count - นับว่าภายใน data source มีข้อมูลอยู่ทั้งหมดเท่าไหร่

    Aggregate - นำข้อมูลทั้ง collection มาดำเนินการแบบต่อเนื่องกัน

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

    บทสรุป Deferred vs Imperative

    จากคำสั่งทั้งหมดที่เขียนมาเป็นตัวอย่าง สุดท้ายการทำงานของมันก็จะตกมาอยู่ในกลุ่ม 3 กลุ่มนั่นเองคือ

    • ทำงานโดยทันที Immediate

    • ไม่ทำงานจนกว่าจะเรียกใช้ Deferred

      • ดึงข้อมูลทั้งหมดมาก่อนค่อยทำงาน Non-Streaming

    var collection = Enumerable.Range(1, 100);
    var qry = collection.Where(it => it % 5 == 0 && it % 7 == 0);
    // ผลลัพท์: { 35, 70 }
    ค่อยทะยอยดึงข้อมูลมาเรื่อยๆ Streaming

    X

    X

    Single numeric value

    X

    X

    X

    X

    X

    X

    X

    TSource

    X

    TSource

    X

    X

    X

    X

    TSource

    X

    TSource

    X

    X

    X

    X

    X

    X

    X

    X

    TSource

    X

    TSource

    X

    X

    Single numeric value, TSource, or TResult

    X

    Single numeric value, TSource, or TResult

    X

    X

    X

    X

    X

    X

    X

    X

    X

    X

    TSource

    X

    TSource

    X

    X

    X

    Single numeric value

    X

    X

    X

    X

    X

    TSource array

    X

    X

    X

    X

    X

    X

    Operators

    Return Type

    Immediate

    Deferred Streaming

    Deferred Non-Streaming

    Aggregate

    TSource

    X

    All

    Boolean

    X

    Microsoft document - Projection Operations
    Microsoft document - Aggregation

    var collection = new int[] { 1, 2, 3, 4, 5 };
    var qry = collection.Select(it => it * 10);
    // ผลลัพท์: { 10, 20, 30, 40, 50 }
    var qry = collection.Select(it => new Student{ Id = it });
    public class Student
    {
        public int Id { get; set; }
    }
    var collection = new[]
    {
        new { Id = 1, Name = "A", Age = 10 },
        new { Id = 2, Name = "B", Age = 15 },
        new { Id = 3, Name = "C", Age = 20 },
    };
    var qry = collection.Select(it => it.Name);
    // ผลลัพท์: { "A", "B", "C" }
    var collection = new[]
    {
        new [] { 1, 2, 3, 4 },
        new [] { 5, 6, 7, 8 },
    };
    var qry = collection.SelectMany(it => it);
    var collection = Enumerable.Range(1, 100);
    var result = collection.First();
    // ผลลัพท์: { 1 }
    var result = collection.FirstOrDefault();
    // ผลลัพท์: { 1 }
    var result = collection.Last();
    // ผลลัพท์: { 100 }
    var result = collection.LastOrDefault();
    // ผลลัพท์: { 100 }
    var result = collection.ElementAt(3);
    // ผลลัพท์: { 4 }
    var result = collection.ElementAtOrDefault(9999);
    // ผลลัพท์: { 0 }
    var collection = Enumerable.Range(1, 100);
    var qry = collection.Take(5);
    // ผลลัพท์: { 1, 2, 3, 4, 5 }
    var qry = collection.TakeLast(5);
    // ผลลัพท์: { 96, 97, 98, 99, 100 }
    var qry = collection.TakeWhile(it => it < 8);
    // ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7 }
    var qry = collection.Skip(4);
    // ผลลัพท์: { 5, 6, 7 ... 100 }
    var qry = collection.SkipLast(4);
    // ผลลัพท์: { 1, 2, 3 ... 96 }
    var qry = collection.SkipWhile(it => it < 50);
    // ผลลัพท์: { 50, 51, 52 ... 100 }
    var a = new[] { 1, 2, 3, 4, 5 };
    var b = new[] { 4, 5, 6, 7, 8 };
    var intersect = a.Intersect(b);
    // ผลลัพท์: { 4, 5 }
    var union = a.Union(b);
    // ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7, 8 }
    var except = a.Except(b);
    // ผลลัพท์: { 1, 2, 3 }
    var collection = new[] { 1, 1, 2, 2, 3, 4, 4, 3 };
    var qry = collection.Distinct();
    // ผลลัพท์: { 1, 2, 3, 4 }
    var collection = new int[] { 7, 5, 2, 6, 4, 1, 3 };
    var ascending = collection.OrderBy(it => it);
    // ผลลัพท์: { 1, 2, 3, 4, 5, 6, 7 }
    var descending = collection.OrderByDescending(it => it);
    // ผลลัพท์: { 7, 6, 5, 4, 3, 2, 1 }
    var reverse = collection.Reverse();
    // ผลลัพท์: { 3, 1, 4, 6, 2, 5, 7 }
    var collection = new[]
    {
        new { Score = 7, Name = "B" },
        new { Score = 3, Name = "A" },
        new { Score = 7, Name = "A" },
        new { Score = 4, Name = "A" },
        new { Score = 3, Name = "C" },
    };
    
    // น้อยไปมาก และ ตามลำดับตัวอักษร
    var ascending = collection
                    .OrderBy(it => it.Score)
                    .ThenBy(it => it.Name);
    
    // มากไปน้อย และ ตามลำดับตัวอักษร
    var descending = collection
                    .OrderByDescending(it => it)
                    .ThenBy(it => it.Name);
    var collection = new int[] { 2, 4, 6, 8, 10 };
    var any = collection.Any(it => it > 9); // true
    var all = collection.All(it => it > 5); // false
    var contain = collection.Contains(8); // true
    var collection = new[]
    {
        new { Name = "A", Age = 15 },
        new { Name = "B", Age = 7 },
        new { Name = "C", Age = 7 },
        new { Name = "D", Age = 15 },
        new { Name = "E", Age = 9 },
    };
    var qry = collection.GroupBy(it => it.Age);
    
    /* ผลลัพท์
    15
    { Name = A, Age = 15 }
    { Name = D, Age = 15 }
    7
    { Name = B, Age = 7 }
    { Name = C, Age = 7 }
    9
    { Name = E, Age = 9 }
    */
    var qry = Enumerable.Range(1, 100);
    // ผลลัพท์: { 1, 2, 3 ... 100 }
    var qry = Enumerable.Empty<int>();
    // ผลลัพท์: { }
    var collection = Enumerable.Empty<int>();
    var qry = collection.DefaultIfEmpty(9);
    // ผลลัพท์: { 9 }
    var qry = Enumerable.Repeat(5, 3);
    // ผลลัพท์: { 5, 5, 5 }
    var collection = new[] { 1, 2, 3, 4, 5 };
    var qry = collection.AsEnumerable();
    var qry = collection.AsQueryable();
    var collection = new[]
    {
        new Dog { Id = 1, OwnerName = "Saladpuk" },
    };
    IEnumerable<Animal> qry = collection.Cast<Animal>();
    public class Animal
    {
        public int Id { get; set; }
    }
    public class Dog : Animal
    {
        public string OwnerName { get; set; }
    }
    var collection = new object[]
    {
        new Dog { OwnerName = "Saladpuk" },
        new Cat { IsFriendly = false },
    };
    var qry = collection.OfType<Dog>();
    // ผลลัพท์: [ { OwnerName = 'Saladpuk' } ]
    public class Dog
    {
        public string OwnerName { get; set; }
    }
    public class Cat
    {
        public bool IsFriendly { get; set; }
    }
    var qry = Enumerable.Range(1, 100);
    int[] result = qry.ToArray();
    var qry = Enumerable.Range(1, 100);
    List<int> result = qry.ToList();
    var collection = new[]
    {
        new { Id = 1, ClassRoom = "A", Name = "Saladpuk" },
        new { Id = 2, ClassRoom = "B", Name = "Thaksin" },
        new { Id = 3, ClassRoom = "C", Name = "Prayut" },
        new { Id = 4, ClassRoom = "B", Name = "Yingluck" },
        new { Id = 5, ClassRoom = "C", Name = "Abhisit" },
    };
    var result = collection
        .GroupBy(it => it.ClassRoom)
        .ToDictionary(it => it.Key, it => it);
    
    /* ผลลัพท์
    A
    { Id = 1, ClassRoom = A, Name = Saladpuk }
    B
    { Id = 2, ClassRoom = B, Name = Thaksin }
    { Id = 4, ClassRoom = B, Name = Yingluck }
    C
    { Id = 3, ClassRoom = C, Name = Prayut }
    { Id = 5, ClassRoom = C, Name = Abhisit }
    */
    var a = new[] { 1, 2, 3, 4, 5 };
    var b = new[] { 4, 5, 6, 7, 8 };
    var qry = a.Concat(b);
    // ผลลัพท์: { 1, 2, 3, 4, 5, 4, 5, 6, 7, 8 }
    var collection = Enumerable.Range(1, 10);
    var result = collection.Sum();
    // ผลลัพท์: 55
    var result = collection.Average();
    // ผลลัพท์: 5.5
    var result = collection.Max();
    // ผลลัพท์: 10
    var result = collection.Min();
    // ผลลัพท์: 1
    var result = collection.Count();
    // ผลลัพท์: 10
    var result = collection.Aggregate((a, b) => a * b);
    // ผลลัพท์: 3628800
    Any
    Boolean
    AsEnumerable
    IEnumerable<T>
    Average
    Cast
    IEnumerable<T>
    Concat
    IEnumerable<T>
    Contains
    Boolean
    Count
    Int32
    DefaultIfEmpty
    IEnumerable<T>
    Distinct
    IEnumerable<T>
    ElementAt
    ElementAtOrDefault
    Empty
    IEnumerable<T>
    Except
    IEnumerable<T>
    First
    FirstOrDefault
    GroupBy
    IEnumerable<T>
    GroupJoin
    IEnumerable<T>
    Intersect
    IEnumerable<T>
    Join
    IEnumerable<T>
    Last
    LastOrDefault
    LongCount
    Int64
    Max
    Min
    OfType
    IEnumerable<T>
    OrderBy
    IOrderedEnumerable<TElement>
    OrderByDescending
    IOrderedEnumerable<TElement>
    Range
    IEnumerable<T>
    Repeat
    IEnumerable<T>
    Reverse
    IEnumerable<T>
    Select
    IEnumerable<T>
    SelectMany
    IEnumerable<T>
    SequenceEqual
    Boolean
    Single
    SingleOrDefault
    Skip
    IEnumerable<T>
    SkipWhile
    IEnumerable<T>
    Sum
    Take
    IEnumerable<T>
    TakeWhile
    IEnumerable<T>
    ThenBy
    IOrderedEnumerable<TElement>
    ThenByDescending
    IOrderedEnumerable<TElement>
    ToArray
    ToDictionary
    Dictionary<TKey,TValue>
    ToList
    IList<T>
    ToLookup
    ILookup<TKey,TElement>
    Union
    IEnumerable<T>
    Where
    IEnumerable<T>