Saladpuk.com
🏆 เนื้อหาหลัก
🏆 เนื้อหาหลัก
  • 💖สลัดผัก
  • 📰มีอะไรใหม่บ้าง
    • 2020
      • 2020-11
      • 2020-10
      • 2020-09
      • 2020-08
      • 2020-03
      • 2020-02
      • 2020-01
    • 2019
      • 2019-12
      • 2019-11
      • 2019-10
      • 2019-09
      • 2019-08
  • 🤔อ่านเรื่องไรดี ?
  • มือใหม่หัดเขียนโค้ด
    • 👶เขียนโค้ดด้วยภาษา C#
      • เกิดมาไม่เคยเขียนโค้ดมาก่อนเบย
      • 👶พื้นฐาน
        • 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
      • 👨⏳ระดับสูง
        • Generic
        • Delegates
        • Action & Func
        • Lambda expression
        • LINQ
        • พระคัมภีร์การใช้คำสั่ง LINQ
      • 💡Tips
        • 💡C# version 8.0
        • 💡Boxing & Unboxing
    • 👶Algorithm
      • 👾Algorithm Big-O
      • 👽Algorithm P & NP
    • 👦OOP
      • 💖Abstraction
      • 💖Encapsulation
      • 🏆Abstraction & Encapsulation
      • 💖Inheritance
      • 💖Polymorphism
      • 🏆Inheritance & Polymorphism
      • 📝ลองเขียน OOP ดูดิ๊
      • 👑OOP + Power of Design
      • 🥰เทคนิคในการออกแบบ
    • 👶บทสรุปฐานข้อมูล
      • เก็บรูปในฐานข้อมูล
      • Database indexing
      • การลบข้อมูล
    • 👦Communication Patterns
    • 👦Design Patterns
      • 🤰Creational Patterns
        • 🏭Factory Method
        • 🏭Abstract Factory
        • ☝️ Singleton Pattern
        • 🏗️ Builder Pattern
        • 🎎Prototype Pattern
      • 🧱Structural Patterns
        • 🔌Adapter Pattern
        • 📪Proxy Pattern
  • Puzzle
    • 🧠Challenges
      • 🐴Google ม้า 25 ตัว
      • 🌉Amazon เสา 2 ต้น
      • 🥇ทองเก๊
      • 💊ยาต้านโควิด
      • 🎩CP หมวก 5 ใบ
      • 🧓Einstein's Riddle 01
  • พื้นฐานที่ควรต้องรู้
    • 🐳Docker
      • 📦Docker Containers
      • 🃏Docker Exercise 01
      • 🛠️ Docker Tools
      • 🗃️ Docker Registry
      • 🖼️ Container Image
      • 📢Docker Push
      • 🔄WSL
    • 👶Clean Code
      • 🧓Uncle Bob - Clean Code
      • 🧓Uncle Bob - Comments
      • 🧓Uncle Bob - Naming
      • 🧓Uncle Bob - Mindset
      • 🧓Uncle Bob - TDD
    • 👶Code Smells
    • 👶สิ่งที่คนเขียนโค้ดมักเข้าใจผิด
    • 👶AI พื้นฐาน
    • 👶Git พื้นฐาน
      • Git branching strategy
    • 👶Cloud พื้นฐาน
    • 👶UML พื้นฐาน
      • Activity Diagram
      • Class Diagram
      • Sequence Diagram
      • Use case Diagram
      • บทสรุปการใช้ UML
    • 👶Data Scientist
      • การเลือก Algorithms ให้ AI (1/5)
      • การเตรียมข้อมูลให้ AI (2/5)
      • หลักการตั้งคำถามให้ AI (3/5)
      • แฉความลับของ AI Model (4/5)
      • หัดเขียน AI จาก AI ของคนอื่น (5/5)
    • 👶DevOps พื้นฐาน
    • 👶Docker ขั้นพื้นฐาน
      • Image and Container
      • แชร์ Docker Image ที่สร้างไว้
    • 👶Microservices พื้นฐาน
      • Microservices ที่ดีมีลักษณะยังไง
      • Microservices Tips
      • จาก Monolith สู่ Microservices
    • 👶ความรู้พื้นฐานในการทำเว็บ
    • 👦Bottlenecks of Software
      • หัวใจที่สำคัญที่สุดของฐานข้อมูล
    • 👦Agile Methodology
      • Agile in a Nutshell
      • Software Development Life Cycle
      • Code Review
    • 👦Security พื้นฐาน
      • การเก็บรหัสผ่านที่ถูกต้อง
      • Security in actions
        • Hash function
      • Security Principles
      • 😎The Matrix 1
      • 😎The Matrix 2
      • HTTPS in a nutshell
    • 👦SOLID Design Principles
      • มารู้จักกับ SOLID กันดีกว่า
      • Single-Responsibility Principle
      • Open/Closed Principle
      • Liskov Substitution Principle
      • Interface Segregation Principle
      • Dependency-Inversion Principle
  • Cloud Computing
    • 👶Microsoft Azure 101
      • สมัคร Microsoft Azure
      • รู้จักกับ Resource Groups
      • สร้างเว็บตัวแรกกัน
      • สร้าง Virtual Machine กัน
      • ประเภทของคลาว์เซอร์วิส
      • มาสร้าง Logic App กัน
      • มาสร้าง Function App กัน
      • คลาว์คิดเงินยังไง ?
      • Cloud Native
      • Guideline for Cloud scaling
      • Auto Scaling
    • 👶Azure App Services
    • 👶App Service Plan
    • 👶Azure Storage
      • Blob storage
        • ลองสร้างที่เก็บไฟล์กันเลย
        • เข้าใจ Blob storage ให้มากขึ้น
        • ลองเขียนโค้ดอัพโหลดไฟล์กันบ้าง
        • สร้างเว็บจากที่ฝากไฟล์บนคลาว์
    • 👶Azure Bot Service
      • Bot เข้าใจเราได้ยังไงกันนะ
    • 👶Azure Cognitive Services
      • การสร้าง Cognitive Services
      • การ Login ด้วยใบหน้า
      • อ่านลายมือจากรูปเป็นตัวอักษร (OCR)
      • เขียน AI แยกของต่างๆทำยังไง?
      • เขียนแอพ ทายอายุ บอกเพศ ง่ายจิ๊ดเดียว
      • เขียนแอพให้ AI อธิบายรูปเป็นภาษาคน
    • 👶Machine Learning Studio
      • มาสร้าง AI ของแท้ตัวแรกของเรากัน
      • สร้าง AI ตัดสินใจอนุมัติบัตรเครดิต 💳
      • ลองเรียกใช้ AI ของเรากัน
    • 👶Azure Service Fabric
      • สร้าง Service Fabric กัน
    • 👶Blockchain
      • Blockchain ทำงานยังไง ?
      • Consensus Algorithm คืออะไร ?
      • สร้าง Blockchain ใช้เองกัน !
      • หัดเขียน Smart Contract กัน
    • 👶Power BI
    • 👶Azure Web App
      • เซิฟเวอร์บนคลาว์ ราคา? ต่าง?
    • 👶Azure DevOps
      • เล่น Azure DevOps กัน
      • เล่นกับ Repository
      • ลองทำ Continuous Integration (CI)
      • ลองทำ Continuous Delivery (CD)
      • เล่น Kanban Board
    • 🤠Cloud Playground
      • การป้องกันความลับหลุดตอนที่ 1
      • การป้องกันความลับหลุดตอนที่ 2
      • การป้องกันความลับหลุดตอนที่ 3
      • การป้องกันความลับหลุดตอนจบ
  • Software Testing
    • 👦Test-First Design
    • 👦Test-Driven Development
      • 1.มารู้จักกับ TDD กันดีกว่า
      • 2.Test cases เขาเขียนกันยังไงนะ
      • 3.เครื่องมือในการทดสอบ
      • 4.การใช้ Theory และ InlineData
      • 5.โค้ดที่ทดสอบได้
      • 6.Mantra of TDD
      • 7.Functional & None-Functional testing
      • 8.Manual vs Automation testing
      • 9.Automation Frameworks in .NET
      • 10.Mock Framework
      • 11.มาเรียนการใช้ Moq กันเถอะ
      • 12.สรุป
  • Web
    • 👦Web API
      • 1.Web API คืออะไร
      • 2.ติดตั้ง .NET Core SDK
      • 3.สร้าง Web API ตัวแรกกัน
      • 4.Verbs
      • 5.Swagger เพื่อคู่ API
      • 6.การใช้ Model
      • 7.เรียก Web API ผ่าน Postman
      • 8.มาจัดกลุ่ม API กัน (1/2)
      • 9.มาจัดกลุ่ม API กัน (2/2)
  • Software Design
    • 🤴Design Patterns
      • 🦈Creational patterns
        • Abstract Factory
        • Builder
        • Factory Method
        • Prototype
        • Singleton
      • 🦈Structural patterns
        • Adapter
        • Bridge
        • Decorator
        • Facade
        • Proxy
      • 🦈Behavioral patterns
        • Chain of Responsibility
        • Command
        • Iterator
        • Mediator
        • Memento
        • Observer
        • State
        • Strategy
        • Template Method
        • Visitor
Powered by GitBook
On this page
  • 🧐 โจทย์
  • 🧒 แก้โจทย์ครั้งที่ 1
  • 🧒 แก้โจทย์ครั้งที่ 2
  • 🔥 วิเคราะห์ปัญหา
  • 🔥 แก้ไขปัญหา
  • 🤔 Factory Method คือไย ?
  • 🔥 จุดกำเหนิด
  • 🔥 ผลจากการใช้
  • 🔥 วิธีการใช้
  • 🤔 ทำไมต้องใช้ด้วย ?
  • 😱 Hardcode
  • 😱 Dependencies
  • 😱 เทสยาก
  • 🤔 ใช้แล้วดียังไง ?
  • 👨‍🔧 Hardcode
  • 👨‍🔧 Dependencies
  • 👨‍🔧 เทสยาก
  • 🎯 บทสรุป
  • 👍 ข้อดี
  • 👎 ข้อเสีย
  • 🤙 ทางเลือก

Was this helpful?

Export as PDF
  1. มือใหม่หัดเขียนโค้ด
  2. Design Patterns
  3. Creational Patterns

Factory Method

แนวคิดในการสร้าง object ที่เหมาะสมกับสถานะการณ์ที่กำลังเป็นอยู่

PreviousCreational PatternsNextAbstract Factory

Last updated 5 years ago

Was this helpful?

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

แนะนำให้อ่าน บทความนี้เป็นส่วนหนึ่งของมหากาพย์ Design Patterns ที่จะมาเป็น guideline ในการแก้ปัญหาในการออกแบบซอฟต์แวร์โปรเจค หากใครสนใจอยากเข้าใจตั้งแต่ต้นว่ามันคืออะไร และเจ้า patterns ทั้ง 23 ตัวมีอะไรบ้าง ก็สามารถจิ้มตรงนี้เพื่อไปอ่านบทความหลักได้เบยครัช

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

เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย และจะเป็นประคุณอันล้นพ้นถ้ากด 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 นะจะได้เข้าใจง่ายๆหน่อย)

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
    {
        // โค้ดคล้ายๆด้านบนแหละลองคิดเล่นๆดู
    }
}

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

Open & Close Principle (OCP) อันนี้เป็นตัวอย่างในการออกแบบที่ละเมิดหลักในการออกแบบที่ชื่อว่า OCP นั่นเอง ซึ่งมันทำให้ทุกครั้งที่มีของใหม่ๆถูกเพิ่มเข้าไปปุ๊ป เราก็ต้องไปแก้โค้ดเดิมเสมอ สังเกตุได้ว่าถ้ามี Slime แบบใหม่เข้ามา หรือมีแผนที่แบบอื่นๆอีก เราก็จะต้องไปไล่แก้เจ้าพวก IF-ELSE ที่อยู่ด้านบนกันใหม่ทุกครั้งนั่นเอง

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

ก่อนที่จะไปทำให้มันสมบูรณ์ ผมจะขอแยกเจ้าเมธอด CreateNewSlime() ออกมาเป็น Model ที่ใช้ในการผลิต Slime ก่อนดีกว่า ซึ่งภาพด้านล่างคือโครงสร้างของ Simple Factory นั่นเอง

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

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

จากปัญหาที่ว่ามาเราจะพบว่า ทุกครั้งที่มีเงื่อนไขใหม่ มี Slime แบบใหม่ มีแผนที่แบบใหม่ หรืออะไรก็ตาม มันจะทำให้ เราต้องไปแก้เจ้าคลาส SimpleSlimeFactory เสมอ เลยทำให้ในอนาคตมันจะ บวมฉ่ำ อย่างไม่ต้องสงสัยเลย

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

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

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

🔥 แก้ไขปัญหา

จากที่ร่ายมาทั้งหมดเราจะสามารถหา พฤติกรรม + หน้าที่การรับผิดชอบ ออกมาได้ว่า

  • แผนที่ มีหน้าที่รับผิดชอบในการ สร้าง Slime

  • แผนที่ ต้องเป็นคน ดูแลเงื่อนไขต่างๆ ในการสร้าง Slime (เช่น เวลา, ความสามารถพิเศษ)

  • Slime เป็นแค่ ผลลัพท์ ที่เราอยากได้เพียงแค่นั้น

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

ซึ่งแต่ละโรงงานก็จะมีหน้าที่คอยดูแลการผลิต Slime ของใครของมันเอง ซึ่งตรงตามหลัก SRP อีกด้วย

แต่มันยังไม่จบเท่านั้น เพราะมันยังขัดกับ OCP (ปัญหาตัวแรกที่อยู่ด้านบน) อยู่เหมือนเดิม เพราะเวลามีอะไรใหม่ๆเข้ามา เราก็ต้องไปไล่แก้โค้ดเก่าของเราอยู่ดี ... แต่ถ้าเรามาคิดดีๆ เราก็จะพบว่า Payon และ Desert นั้นมันก็ เป็นโรงงานทั้งคู่ อยู่แล้ว ดังนั้นมันก็น่าจะรวมร่างเป็นแบบนี้ได้นะ

แต่จากรูปด้านบนจะเห็นว่าเมธอด CreateNewSlime() ที่อยู่ใน Base class (เส้นสีแดง) นั้น มันไม่รู้ว่าจะต้องสร้าง Slime จริงๆยังไง เพราะ Sub class ของมันต่างหาก (เส้นสีเขียว) คือตัวที่รู้ว่าในสถานะการณ์นี้จะต้องสร้าง Slime อะไรออกมา ดังนั้น Base class ควรจะปล่อยให้ Sub class ทำหน้าที่ในการสร้าง Slime ไปซะ เลยทำให้เราต้องเพิ่ม Abstract method เข้ามาอีกตัวให้ sub class รับผิดชอบเรื่องการสร้าง slime นั่นเอง

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 แหละลองไปคิดเล่นๆดู
    }
}

ยินดีด้วยในตอนนี้คุณได้ใช้สิ่งที่เรียกว่า Factory Method Pattern เรียบร้อยแล้ว ไม่ว่าจะรู้ตัวหรือไม่ก็ตาม เย่ๆ 👏

🤔 Factory Method คือไย ?

🔥 จุดกำเหนิด

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

สามารถสร้าง 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

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

var animal = new Dog();

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

😱 Dependencies

สมมุติว่าเราต้องไปสร้าง object ของคลาสด้านล่างนี้ โดยที่มันมี Constructor หน้าตาประมาณนี้

public class DatabaseConnector
{
    public DatabaseConnector(IConnection conn,
        IResource resource,
        IProxy proxy,
        ICancelationToken cancelation,
        ..และอื่นๆ..)
    {
        // ทำอะไรซักอย่างไม่ต้องไปสนใจ
    }
}

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

😱 เทสยาก

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

var dbConn = new MySqlConnector();

คำถามคือแล้วเวลาที่เราจะเทสมันล่ะ เราจะต้องยิงไปที่ตัว database จริงเพื่อเทสเลยเหรอ? หรือถ้าเราจะ Mock มันล่ะจะทำยังไง?

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

🤔 ใช้แล้วดียังไง ?

อย่างที่บอกว่า Design Pattern ตัวนี้ มันจะทำให้โค้ดของเรา มันลดการผูกกันของโค้ด (decoupling) ดังนั้นปัญหาที่ว่าด้านบนมันจะละลายหายไปจนหมดเลย ตามนี้

👨‍🔧 Hardcode

จากเดิมแทนที่เราจะใช้คำสั่ง new ไปสร้าง object ให้มันผูกติดกับคลาสนั้นๆเอง มันก็จะเปลายเป็นแบบนี้

var animal = animalFactory.GetAnAnimal();

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

👨‍🔧 Dependencies

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

var dbConn = dbConnectorFactory.CreateConnector();

👨‍🔧 เทสยาก

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

var dbConn = dbConnectorFactory.CreateConnector();

// เล่นตามเกมก็ได้
public class MockDbConnectorFactory : DbConnector { ... }

// หรือจะส่ง mock object เข้าไปแทนที่ผ่าน constructor ก็ได้
Constructor( mockFactory )

🎯 บทสรุป

👍 ข้อดี

การนำ Factory Method Pattern มาใช้งานนั้นจะช่วย ลดการผูกกันของโค้ดลง ทำให้เราสามารถเปลี่ยนแปลง แก้ไข รองรับสิ่งต่างๆได้มากขึ้น และมันยังช่วยเปิดให้เราทำพวก Inversion of Control (IoC) ได้ง่ายขึ้นด้วย

👎 ข้อเสีย

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

🤙 ทางเลือก

เราสามารถนำ Framework พวก Dependency Injection (DI) เข้ามาใช้แทนได้นะจ๊ะ โค้ดกระชับหลับสบายเต็มตื่นด้วย

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

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

Single-Responsibility Principle (SRP) เจ้าสิ่งนั้นๆควรมีหน้าที่รับผิดชอบเพียงอย่างเดียว ซึ่งเจ้าโรงงานแต่ละโรงงานด้านบน มันก็จะดูแลแค่หน้าที่การผลิต Slime เพียงอย่างเดียวเท่านั้น ซึ่งถ้าเงื่อนไขในการผลิต Slime ของโรงงาน Desert เปลี่ยนก็ไม่ไปกระทบอะไรกับโรงงาน Payon นั่นเอง ส่วนใครที่ลืมหรืออยากทบทวนเรื่อง SRP สามารถเข้าไปอ่านได้จากลิงค์นี้เบย

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

Dependency-Inversion Principle (DIP) เป็นหนึ่งในหัวใจของการออกแบบให้เราไม่ไปผูกการทำงานไว้กับ Low level module นั่นเอง ส่วนใครที่อยากศึกษาเรื่องนี้เพิ่มเติมก็สามารถกดไปอ่านได้จากลิงค์นี้เบย

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

เกลียด ชอบ ถูกใจ อยากติดตาม อยากติชมแนะนำด่าทอ หรืออะไรก็แล้วแต่ (ห้ามมายืมเงิน) จิ้มลงมาที่เพจนี้ได้เลย และจะเป็นประคุณอันล้นพ้นถ้ากด Like + Follow + Share ให้ด้วยขอรับ น้ำตาจิไหล 🥺

👦
🤰
🏭
Open & Close Principle
Single-Responsibility Principle
Program to an interface and not to an implementation.
Dependency-Inversion Principle
Abstraction
Encapsulation
Mr.Saladpuk
Creational Patterns
👦 Design Patterns
Mr.Saladpuk
Simple Factory หรือ รูปแบบอย่างง่ายของ Factory Pattern ที่ยังไม่สมบูรณ์นั่นเอง
SimpleSlimeFactory จะบวมขึ้นไปเรื่อยๆถ้า Slime เพิ่มขึ้น หรือ แผนที่เพิ่มขึ้น