สวัสดีครับวันนี้ผมจะมาแนะนำ Design Pattern ตัวหนึ่งที่มีชื่อว่า Strategy โดยผมอยากจะอธิบายความหมาย และประโยชน์ของ Design Pattern ตัวนี้อย่างง่ายๆ จากตัวอย่าง Code ที่จะนำมาให้ดูกันครับ
เอาหล่ะ เราจะเริ่มจากโจทย์ที่เรากำลังจะแก้ปัญหากันก่อน ลองสมมติว่าตอนนี้เรากำลังเขียนระบบจัดการ Order สินค้าอยู่นะครับ โดย Feature ที่เรากำลังทำอยู่นั้นคือส่วนจัดการการขนส่ง โดยเราจะต้องเชื่อมต่อกับ API ของขนส่งด้วย (แบบสมมตินะครับ) มาดู Code กันเลย
เริ่มจาก Class Order ตรงนี้ไม่มีอะไรมาก เป็นแค่ Object เก็บข้อมูลทั่วๆไป
ถัดมาคือตัว OrdersService ที่มี function ship ทำหน้าที่จัดการการขนส่ง โดยรับ Order ที่ต้อง Ship เข้าไป แล้วทำการยิงข้อมูลไปที่ API ของขนส่ง จากนั้นเราจะได้ Tracking Number เป็น Response ตอบกลับมา เอาเป็นว่าเป็นเสร็จพิธี
ผลลัพธ์จากการ Run Code จะได้หน้าตาประมาณนี้ so far so good ครับ
โอเค ทีนี้ เราอาจจะได้รับ Requirement มาประมาณว่า ระบบจะต้องสามารถเลือกผู้ให้บริการขนส่งได้ตามแต่ละ Order โดยเราจะต้องเชื่อม API ของขนส่งแต่ละเจ้าด้วย
สมมติว่าเราต้องเชื่อม API ขนส่งทั้งหมด 3 เจ้า (และแน่นอนจะมีเพิ่มขึ้นในอนาคต) ให้ชื่อว่า 1. Slowly Express 2. Fat Express และ 3. Samurai Van ซึ่งมีรายละเอียดของการเชื่อมต่อ API ดังนี้
และเพื่อความง่ายของบทความนี้เราจะให้ API Response ของทุกเจ้า Return Tracking Number format ต่างๆ เหมือนกันหมด
เอาหล่ะ ลองทำความเข้าใจ API พวกนี้ดูสักครู่ อย่าถามหาถึงความ Make Sense มากนัก เอาเป็นว่าเอาเวลาไปเขียน Code ดีกว่า
ในส่วนของ Class Order เราได้ทำการเพิ่มตัวแปร courierName เพื่อเก็บชื่อของขนส่ง และ Note ไว้สักนิดว่า น้ำหนักที่เก็บไว้มีหน่วยเป็น กิโลกรัม
สำหรับ OrdersService เราได้ทำการเพิ่มในส่วนของ if-else ขึ้นมา เพื่อเช็คว่า Order นั้นๆ ใช้ขนส่งเจ้าไหน เราจะได้ทำการเตรียม Parameters และ ยิงไปยัง API ของเจ้านั้นๆ ต่อไปเราจะลองทดสอบดูการทำงานของ Order ที่มีการใช้ขนส่งแต่ละเจ้าต่างกัน
ทำการ set Order ตามนี้ และลองทดสอบ Code ดู
จากผลลัพธ์จะเห็นได้ว่า Code ได้แยกการทำงาน if-else ถูกต้องตามแต่ละขนส่งที่ set ไป จุดสังเกตุคือ Tracking Number ที่ได้มาจาก API นั้นมี Format ที่แตกต่างกันเล็กน้อย ตามแต่ละขนส่ง
ทุกอย่างดูเหมือนจะเรียบร้อย Code ก็ทำงานได้ปกติ แล้วปัญหามันอยู่ตรงไหน ผมอยากจะยกประเด็น 3 ข้อขึ้นมาให้ดูครับ
1. การทำงานที่เหมือนกันแต่ไม่เหมือนกัน อันนี้อาจจะไม่ใช่ปัญหา แต่เป็นลักษณะของ Code ชุดนี้ ลักษณะก็คือ เรารับ Order เข้ามา เพื่อทำการ Ship โดยมีการยิง API ไปที่ขนส่งแต่ละเจ้า และรับ Tracking มาแสดงผลเหมือนกัน แต่ที่ไม่เหมือนกันคือ รายละเอียดการเตรียม Parameters สำหรับยิงไป API ขนส่ง และ Endpoint ของแต่ละขนส่ง หมายความว่า การทำงาน High Level เหมือนกัน แต่ Implementation Detail ไม่เหมือนกัน ซึ่งถ้าเราคุ้นเคยกับ Concept ของ OOP มาบ้าง เรื่องนี้ก็คือ Polymorphism นั่นเองครับ
2. Code อ่านยากใน Class OrdersService Function ship เราจะเห็นว่ามี Code สำหรับแต่ละขนส่งติดกันเป็นพรืดๆ โดยมี if-else เพื่อเป็นตัวแบ่งการทำงานของแต่ละขนส่งอยู่ เอาจริงๆ จาก Code ตัวอย่างที่เราเขียนมา ก็สามารถอ่าน และทำความเข้าใจได้ไม่อยากหรอกครับ แต่ลองจินตนาการดูว่า ถ้านี่เป็น Code บนโลกแห่งความเป็นจริง ที่จะมีส่วนการทำ Authentication หรือ มี Flow การทำงานที่ซับซ้อนกว่านี้ Code นี้อาจจะยาวเป็นพันๆ บรรทัดได้เลยครับ ลองคิดดูว่า Code ยาวเป็นพรืดๆ และส่วนกั้นแต่ละ Flow เป็นแค่ if-else บรรทัดเดียว อาจจะทำให้หลง และงงได้ง่ายๆ เลยนะครับว่าเรากำลังอ่าน Flow ของขนส่งเจ้าไหนอยู่
3. เกิดผลกระทบและข้อผิดพลาดได้ง่ายเมื่อต้องแก้ไข Code จริงๆแล้วถ้าเราจะแก้ไข Code หรือว่าจะเพิ่มขนส่งอีกสักเจ้าก็คงจะทำได้ไม่ยากนัก อาจจะใช้เวลาหา Code นานสักหน่อย แต่เรากำลังพูดถึงผลกระทบ และข้อผิดพลาดที่อาจจะตามมาจากการแก้ไข หรือ เพิ่มเติม Code ครับ ลองจินตนาการดูว่า ถ้า Team ของเรามีการเข้ามาแก้ไข Code ชุดนี้พร้อมๆกัน ก็อาจจะทำให้เกิด Merge Conflict ได้ง่ายๆ และการเพิ่มขนส่งเจ้าใหม่เข้าไปก็จะยิ่งจะทำให้ Code นี้ยาวขึ้นอีกเรื่อยๆ ไม่รู้จบ ทำให้การ Maintenance Code ชุดนี้ไม่สนุกแน่ๆครับ และยังส่งผลต่อระยะเวลาที่ใช้ในการเพิ่ม Feature หรือ Fix Bug อีกด้วย
เอาหล่ะครับ ตอนนี้เรากำลังจะเข้าเนื้อหาหลักกันแล้ว หลังจากเกริ่นมาซะนาน ซึ่งเราจะนำ Design Pattern ที่มีชื่อว่า Strategy มาแก้ไขปัญหาที่เราได้ยกประเด็นกันขึ้นมาครับ แต่ว่าตอนนี้บทความนี้น่าจะยาวไปซะแล้ว เอาเป็นว่า เดี๋ยวเรามาเจอเจ้า Strategy ตัวเป็นๆกันในบทความต่อไปกันดีกว่าครับ เอ๊ะ! จริงๆแล้วบทความนี้ Headline ว่าจะพูดถึง Strategy แต่ไม่ได้พูดถึง Strategy กันเลยนะครับเนี่ย ฮ่าๆ
0 Comment