Design Patterns RECAP

ไปสัมภาษณ์งานมา 2 ที่, ถามถึง Design Patterns 2 ที่, ตอบไม่ได้ทั้งสองที่! (ไม่รู้แม้กระทั่งว่ามันคืออะไร!)

เลยคิดว่า ควรค่าแก่การศึกษาแล้ว เลยขวานหาหนังสืออ่าน ไปเจอเล่ม “PHP Design Patterns” ของ O’ Reilly มา อ่านยากมากกกก ใครอ่านไม่เข้าใจ ไม่ต้องแปลกใจ ฝรั่งยั่งบ่น!

ก็เลยหาเล่มอื่น เซิชไปเซิชมา ก็เจอ กระทู้นี้ใน StackOverflow เขาก็แนะนำเล่ม “Head First Design Patterns” (เห็นเล่มนี้ตั้งนานแล้วแหละ แต่ไม่กะหยิบมาอ่านเล้ย… ปกแย่มาก) ก็เลยเอาวะ ลองดู! ผลปรากฎว่า เป็นหนังสือที่เขียนเข้าใจง่ายมากๆ เลยครับ! คนเขียนเข้าใจถ่องแท้จริงๆ ยกตัวอย่างก็เข้าใจง่าย ดีงาม ตอนนี้อ่านไปได้ 70% ละ ขอสรุปไว้ก่อน กลัวลืม เพราะมันมี Patterns เยอะจริงๆ

Design Patterns คืออะไร?

Design Patterns คือ แนวทางการเขียนโปรแกรมเพื่อแก้โจทย์ๆ หนึ่ง ถามว่าไม่รู้จัก Design Pattern แล้วเขียนโปรแกรมได้ไหม, แน่นอนว่าเขียนได้ครับ แต่มันจะเขียนได้ไม่ดี, ไม่ยืดหยุ่น, ไม่ได้เขียนตามแบบแผนที่เขาศึกษากันมาเนิ่นนาน เวลาจะถ่ายงานให้ใคร (หรือรับงานจากคนอื่นต่อมา) ถ้าใช้หลัก Design Patterns มาช่วย เราก็จะเรียนรู้ Code ได้อย่างรวดเร็ว เช่น Class นี้ ใช้ Patterns แบบ State เราก็จะ อ๋อ ทันที ว่ามันทำงานยังไง สรุปก็คือ ควรจะศึกษา เพื่อประโยชน์ต่อตัวเอง และคนอื่นครับ!

ช่วยยกตัวอย่างให้เห็นภาพมาชัดๆ เลย ได้ไหม

ง่ายสุดคือ Singleton ครับ… มันคือ Pattern แบบนึง ที่คำนึงถึงว่า ทั้ง Application ต้องเข้าถึง Object ตัวนี้ ได้แค่ตัวมันคนเดียวเท่านั้น ห้ามมีการ New Object ตัวที่ 2 โผล่ออกมาให้เห็นใน Application นี้ การใช้งานก็เช่น พวกที่เราไม่อยากให้ Object มาสร้างปัญหาเวลาทำงานหลาย Thread, เช่นพวก Database หรือ Logger อะรไงี้ ตัวอย่าง Code ก็ง่ายๆ ตามรูปประกอบ

Screen Shot 2558-10-17 at 5.03.45 PM

ง่ายจัง! คุณรู้จัก Design Pattern 1 อย่างแล้วเห็นมั้ย!

Design Patterns อื่นๆ หละ?

มันก็มีอีกหลาย Patterns ครับ อย่างที่บอกว่า แต่ละ Patterns มันก็ถูกออกแบบมาแก้ไขโจทย์ที่ต่างกัน ก็ลองมาดูกันหน่อย ว่ามันมีไว้เพื่ออะไรกันบ้าง (ส่วนหน้าตาเป็นยังไง, Class Diagram แสดงแบบไหน เปิดหนังสือตามเลยจ้า~) จะแบ่งออกเป็น 3 หมวดหมู่ใหญ่ๆ ตามรายละเอียดด้านล่างครับ

Creational

  1. Factory Pattern: อันนี้น่าจะง่ายรองลงมาจาก Singleton, เป็นการสร้าง Class ขึ้นมา Class นึง เพื่อทำหน้าที่สร้าง Object ขึ้นมา (เพราะกระบวนการสร้าง Object มันอาจจะซับซ้อน)
  • ตัวอย่าง: Class สร้าง Pizza, เพราะ Pizza มันมีหลายหน้า และหน้าเดียวกัน แต่คนละเมือง อาจจะมีเครื่องปรุงต่างกันด้วย ก็เลนสร้าง Class Factory มา 2 สาขา (NY, Chicago) แล้วก็ สร้าง Method createPizza() ที่รับ Parameter เป็นชื่อของหน้า Pizza นั้นๆ ไป ก็จะทำให้ Code เราสวยงาม งายต่อการ Maintain กว่ามานั่งเขียน If/else แยกหน้า แยกสาขา เยอะ
  1. Singleton: อันนี้ง่ายสุดละ ตัวเดียวอันเดียว ทั้ง Application
  • ตัวอย่าง: เวลาจะเรียก object ที่ติดต่อ Database ก็ควรเรียกผ่านอะไรทำนอง getInstance() เพราะถ้าจะ new Db() กันทุกรอบนี้ พวกเรื่อง transaction หรือ ข้อมูลชนกัน (ยัง update ไม่เสร็จ แต่ read ไปแล้ว?) ยุ่งเหยิงแน่นอน

Behavioural

  1. Strategy Pattern: เป็น Pattern ที่เอา Interface มาช่วย เพื่อแยก (decouple) ส่วน Client ออกจากส่วนการทำงาน ให้มันเป็นอิสระต่อกัน จะได้ปรับเปลี่ยน (interchangable) การทำงานเหล่านั้น ขณะ Runtime ได้
  • ตัวอย่าง: Module ที่เกี่ยวกับการจ่ายเงิน จะต้องรองรับการจ่ายเงินแต่ละแบบ ซึ่งแต่ละแบบนั่นแหละ คือ แต่ละ Strategy (ตัวอย่างโปรแกรม)
  1. State Pattern: ในหนังสือบอกว่า มันเหมือน Strategy Pattern เลย ถ้ามองเฉพาะ Class Diagram แต่ว่าจุดประสงค์การใช้งานต่างกัน กล่าวคือ ตัว State มันมักจะผูกกับ Context และสองสิ่งนี้แหละจะคอยเปลี่ยน State ต่างๆ เพื่อให้พฤติกรรมนั้น แตกต่างกันออกไป เมื่อ State เปลี่ยน​
  • ตัวอย่าง: ตู้กดน้ำ (Vending Machine) ต่างๆ มันจะมีสถานะ (State) ต่างๆ ของตู้ เช่น สถานะพร้อมใช้งาน (Ready), สถานะรับเหรียญ (Has Coins), สถานะขาย (Sold), สถานะของหมด (Sold out) ซึ่งแต่ละสถานะ ก็จะต้องมีพฤติกรรมต่างกัน เช่น เมื่อมี Action หยอดเหรียญ​, ถ้าตู้อยู่สถานะ Ready ก็จะกลายเป็น Has Coins แต่ ถ้าตู้อยู่สถาน Sold out ก็ควรจะคายเหรียญออกมา และไม่ปรับไปสถานะ Has Coins เป็นต้น
  1. Observer Pattern: เป็น Pattern ที่เชื่อมโยง Object กันแบบ One-to-Many เมื่อ​ Subject มีการเปลี่ยนแปลง เหล่า Observer ทั้งหลายที่ Subscribe ก็จะรับรู้การเปลี่ยนแปลงนั้น ซึ่งจะเอาไปทำอะไรต่อ ก็แล้วแต่ตัว Observer ละ
  • ตัวอย่าง: Push Notification ของ iPhone นี่เลย ง่ายสุดละ… หรือไม่ก็หนังสือพิมพ์, Newsletter ที่เราจะต้องมีการ Subscribe ไปเป็น Observer ของ Subject นั้นๆ ก่อน และเมื่อ Subject นั้นมีการเปลี่ยนแปลง (เช่น เล่มใหม่ออกแล้ว) ก็จะ noti มาบอกเหล่า Observers นั่นเอง
  1. Command Pattern: ห่อหุ้ม (Encapsulate) พวก Request ต่างๆ ให้เป็น Object เพื่อง่ายต่อการจัดการ เช่น ทำเป็น Queue, log request, หรือกระทั่งพวก Undo
  • ตัวอย่าง: Remote control มี slot ปุ่มต่างๆ อยากเปิดทีวี ก็ทำ TVCommand อยากเปิดไฟ ก็ทำ LightCommand แล้ว พอกดปุ่มที่รีโมท ก็เรียก Abstract Method execute() ของแต่ละ Command นั่น ง่ายๆ
  1. Template Method: เหมือนสร้าง Class มา แล้วรอ ให้คนใช้เอาไป Override บาง method หนะ
  • ตัวอย่าง: การชงกาแฟ กับการชงชา มันคล้ายๆ กัน (เช่น ช่วงต้มน้ำ, ช่วงเทใสแก้ว) ต่างกันแค่ ตอน Brew กับ ตอน เติมส่วนประกอบ อะไรงี้ มันก็เลยมี Class ที่ทำหน้าที่เป็น Template แล้วก็ set method ที่เหมือนกันไว้ให้ก่อน รอ override method ที่ต่างกัน
  1. Iterator and Composite Pattern: อันนี้ยากหน่อย, Iterator ก็คือทำ Class พวก Collection ต่างๆ ให้มันมารวมกันได้ โดยที่ไม่ต้องรู้ว่า ค่าจริงๆ ของ collection นั้น มันมีรูปแบบการเก้บข้อมูลอย่างไร (Array, LinkedList, HashTable?) เราจะได้ Traverse ไปทุกๆ element ได้หมดนั่นแหละ ซึ่งก็จะมี Composite มาเกี่ยว ในกรณีที่ การ Traverse นั้น มัน Recursive เช่น ใน 1 เมนู มี Sub menu มันก็ควรจะให้ Menu Item กับ Sub Menu มี Parent Class เดียวกัน จะได้ Traverse หากันง่ายๆ แต่ method ที่เป็น abstract บางอัน แล้วทำงานไม่เหมือนกัน ระหว่าง Menu Item กับ Sub Menu ก็ต้อง throw exception ออกมา

Structural

  1. Decorating Pattern: สร้าง Class มาครอบ Object อีกที เพื่อเพิ่มขีดความสามารถมัน
  • ตัวอย่าง: Class อย่าง FileInputStream ใน Java จะเป็น Component ที่จะถูก Decorated โดย Class อย่าง BufferedInputStream หรือ LineNumberInputStream เป็นต้น
  1. Adapter Pattern: สร้าง Class ที่เป็น Adapter เพื่อให้มันเชื่อมถึงกันได้ (นึกถึงปลั๊กไฟ Universal เลย)
  • ตัวอย่าง: คือบางที เราได้ Library มาตัวนึง เราอาจจะสร้าง Adapter Class มาก่อน แล้วเรียกใช้งานผ่าน Adapter Class ของเรา เพื่อว่า ในกรณีเปลี่ยนแปลง Library นั้น ก็จะได้เปลี่ยนแปลงแค่ตรง Adapter Class ไม่ต้องเปลี่ยนที่ Code จริง เช่น Class Iterator กับ Enumerator ถ้าจะใช้งานร่วมกัน ก็ต้องมี Adapter มาช่วย
  1. Facade Pattern: เป็นการ Wrap Objects หลายๆ ตัว แล้วสร้าง Method การทำงานง่ายๆ ขึ้นมาแทน (Simplify the process)
  • ตัวอย่าง: สมมติเรามี Class ทีวี, Class แอร์, Class ลำโพง, Class Amplifier, Class ไฟ แล้วถ้าเราจะ Turn on Home Theatre, ต้องมาทั้ง activate ทีละ class ซึ่งเหนื่อย และโอกาสผิดพลาดสูง เราสร้าง HomeTheatreFacade ขึ้นมาครอบ Class เหล่านั้น และสร้าง Method “TurnOn” เลย จะง่ายกว่า

อย่างไรก็ตาม

บทสรุปเขาก็พูดว่า อย่าใช้ Design Patterns “เยอะ” เกินไป ใช้แต่พอดีๆ อย่าไป obsess มันมาก ไม่งั้นจะเพิ่มความยุ่งยาก (Complexity) ให้กับ Applciation เราโดยไม่จำเป็น กลายเป็นขี่ช้างจับตั๊กแตน (Overkill) สรุป ก็ให้ใช้เฉพาะส่วนที่ต้องใช้เท่านั้น (เกิดการเปลี่ยนแปลงปล่อย) เอาแค่พอดีๆ

Concept อื่นๆ ที่ควรต้องรู้

ในหนังสือพยายามเน้น คติ (Principle) อื่นๆ ที่น่าสนใจ และควรจะคำนึงถึงไว้ตอนเขียนโปรแกรม เช่น

  • Single Responsibility: Class นึง มันควรจะมีหน้าที่เดียว และหากมีเหตุต้องเปลี่ยน ก็ควรเป็นเหตุผลเดียวเท่านั้น ถ้ามีมากกว่า 1 แปลว่า คุณออกแบบ Class ผิด, ให้แตก Class ออกมา
  • Prefer composition over inheritance: เขาบอกว่า เขาชอบการ Implement interface (หรือ abstraction) มากกว่าการทำ Inheritance เพราะว่ามันยืดหยุ่นกว่ามาก
  • Depend on abstraction. Do not depend on concrete class เช่น อย่าง case pizza ใน factory pattern ก็อย่าไประบุถึงขนาดหน้า Pizza ลงใน code ส่วน client, ใส่แค่ส่วน abstract มันเช่น Pizza เฉยๆ มันจะช่วยทำให้เราเปลี่ยนแปลงการทำงานในช่วง Runtime ได้
  • Class should open for extension but closed for modification.
  • Principle of Least Knowledge: (Law of Delimiter)ในแต่ละ Class ออกแบบให้มัน รู้ให้น้อยที่สุด พึ่งพา Class อื่นให้น้อยที่สุด เท่าที่เป็นไปได้
ถ้าถูกใจบทความ ขอฝากกดไลค์ Facebook Page เตาะแตะต๊อกแต๊ก ทีนะคร๊าบบ https://www.facebook.com/tortaetoktak/ เป็นกำลังใจให้ผู้เขียนสุดพลัง :-)
One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.