SOLID หลักการพื้นฐานที่โปรแกรมเมอร์ควรรู้ (ตอนที่ 2 – OCP)

หลังจากเราได้ทราบถึงหลักการแรกของ SOLID Principles กันไปแล้วนั่นก็คือ Single Responsibility (SRP) ซึ่งพูดถึงการกำหนดขอบเขตการทำงานและหน้าที่ของ Class ให้มีเพียงอย่างเดียว สำหรับบทความนี้เราจะมาดูหลักการที่ชื่อว่า Open Closed Principle (OCP) กันครับ

สารบัญสำหรับตอนอื่นๆ

Open Closed Principle (OCP)

หลักการ SOLID ข้อนี้ได้กล่าวไว้ว่า

code is open for extension but closed for modification

ซึ่งก็หมายความว่า

โค้ดนั้นต้องเปิดรับต่อส่วนขยาย แต่ปิดสำหรับการแก้ไข

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

มาลองดูโค้ดตัวอย่างกันดีกว่าครับเพื่อความเข้าใจที่ง่ายขึ้น โดยเราจะใช้ Class จากบทความที่แล้วเรื่อง SRP เป็นโค้ดเริ่มต้น ซึ่ง Class มีหน้าที่ในการจัดการ การสั่งซื้อสินค้า (Order) ตามด้านล่าง

สำหรับบทความนี้เราจะเพิ่ม Requirement ใหม่เข้าไปคือ การลดราคาสินค้าทุกชนิดอีก 15% เป็นเวลา 1 วันสำหรับวันวาเลนไทน์ ซึ่งนั่นก็เป็น Requirement ที่ไม่ได้ซับซ้อนอะไร สิ่งที่ต้องทำก็คือการเช็ควันและทำการลดราคาตามโค้ดด้านล่าง (บรรทัดที่ 21-27)

เราอาจจะเห็นว่าโค้ดยังคงอ่านง่ายและไม่มีอะไรผิด แต่ถ้าในอนาคตเราต้องการที่จะมีส่วนลดเพิ่มเติมหรืออาจจะมี Business Logic ที่เพิ่มขึ้นจากการเติบโตของธุรกิจ เราจำเป็นที่จะต้องเพิ่ม Code เข้าไปเรื่อยๆ ใน Method นี้ สุดท้ายแล้วก็จะทำให้ Method นี้ก็มีความซับซ้อนเพิ่มขึ้นเรื่อยๆ ซึ่งทำให้ยากต่อการแก้ไข มากไปกว่านั้น Class นี้ยังฝ่าฝืนหลัก SRP  อีกด้วยเนื่องจาก OrderProcessor  ไม่ควรมีหน้าที่ในการคำนวณส่วนลด ดังนั้นตามหลัก SRP และ OCP ของ SOLID Principles แล้วเราควรหลีกเลี่ยงการแก้ไขโค้ดให้มากที่สุด เพราะการแก้ไขโค้ดนั้นถือว่าเป็นการเพิ่มความเสี่ยงให้ระบบเราเกิดข้อผิดพลาด

เริ่ม Refactor…

จากหลัก OCP ที่แล้วสิ่งที่เราจะทำคือการแยกหน้าที่ในการคำนวณส่วนลดออกไปจาก OrderProcessor  และทำให้ OrderProcessor  นั้นยืดหยุ่นต่อการเพิ่มหรือลดวิธีการคำนวณส่วนลด โดยเราจะสร้าง Class ขึ้นใหม่อีก 2 Class คือ CategoryDiscounter  และ ValentineDiscounter

// CategoryDiscounter

// ValentineDiscounter

หลังจากนั้นก็แก้ไขให้ OrderProcessor  รับ array ของ Class ที่ทำหน้าที่ในการคำนวณส่วนลดผ่าน Constructor

เพิ่มเติม : สำหรับทั้งสอง Class ด้านบนจริงๆ แล้วเราควรเขียน Interface เพิ่ม โดยอาจจะตั้งชื่อว่า DiscounterInterface  โดยมี public function discount(Order $order);  แล้วให้ทั้งสอง Class นั้น Implements แต่สำหรับบทความนี้ขอข้ามเรื่อง Interface ไปง่ายต่อความเข้าใจครับ

และปรับแก้ไข Method process()

มาลองเรียก Class ที่เราสร้างขึ้นมาดูกันครับ

เพิ่มเติม : โดยส่วนมากแล้ว Framework สมัยใหม่ต่างๆ จะใช้หลักการที่ชื่อว่า Dependency Injection ซึ่งจะทำให้เราไม่จำเป็นต้องทำการสร้าง Class และส่งเข้าไปเหมือนตัวอย่างด้านบน เช่น สำหรับ Laravel Framework การ Register ตัว Dependency ต่างๆ จะทำผ่าน Service Provider

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

  1. สร้าง Class สำหรับการลดราคาใหม่ เช่น SongkranDiscounter
  2. เพิ่ม Class เข้าไปใน array discounters

ในทางกลับกันถ้าเราอยากยกเลิกส่วนลดไหนสิ่งที่เราต้องทำก็คือแค่ลบ Class นั้นๆ ออกจาก array เท่านั้นเอง

เพียงเท่านี้ Class ของเราตอนนี้ก็จะเป็นไปตามหลัก OCP ที่บอกว่า Class ของเรานั้นควรจะเปิดต่อการต่อเติม (นั่นก็คือการเพิ่ม Class การคำนวณส่วนลดได้ตามที่เราต้องการ) และปิดต่อการแก้ไข (ซึ่งการเพิ่มหรือลด Class สำหรับการคำนวณส่วนลดนั้นสามารถทำได้โดยที่ไม่ต้องแก้ไข OrderProcessor  เลย)

มุมมอง TDD

สิ่งที่น่าสนใจก็คือเมื่อเราทำ Class ของเราให้ตามหลัก OCP แล้ว Class ของเราก็จะเป็นไปตามหลัก SRP ไปด้วยโดยอัตโนมัติ คือ  CategoryDiscounter  มีหน้าที่เดียวคือการคำนวณส่วนลดตาม Category ที่เรากำหนดไว้ หรือ ValentineDiscounter  ก็มีหน้าที่เพียงลดราคาสินค้าสำหรับวันวาเลนไทน์ ซึ่ง! เมื่อหากเราต้องการเขียน Unit Test เราสามารถ Focus เฉพาะหน้าที่ของ Class นั้นๆ ได้อย่างง่าย เช่น แทนที่เราต้องเขียน Test Scenario ในการเช็คว่าราคาสินค้าถูกลดแล้วหรือยังใน OrderProcessorTest  เราสามารถไปแยกเขียนตามเฉพาะ CategoryDiscounterTest  และ ValentineDiscounterTest  ได้เลย

บทสรุป

ในความคิดเห็นส่วนตัวหลัก SRP และ OCP นี้เป็นหลักการพื้นฐานที่เราควรจะทำตามตลอดไม่ว่าระบบที่เราพัฒนาจะใหญ่หรือจะเล็ก เนื่องจากสองหลักการนี้เป็นหลักการที่ทำตามได้ไม่ยากและให้ผลลัพธ์ค่อนข้างชัดเจนทั้งในเรื่องของความง่ายในการในการอ่านโค้ด (Code Readability) และการเขียน Test

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

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

อ้างอิง

  1. https://leanpub.com/laravel
prapat