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

และก็มาถึงหลักการข้อสุดท้ายของ SOLID Principles ที่ชื่อว่า Dependency Inversion Principle (DIP) กันแล้ว มีหลายคนอาจจะสับสนกับอีกหลักการที่ชื่อว่า Dependency Injection ซึ่งจริงๆ แล้วสองหลักการนี้ไม่เหมือนกันนะครับ

Dependency Inversion != Dependency Injection

แต่ก็ยังมีความเกี่ยวเนื่องกันเพราะว่าการที่เราจะทำตามหลักการ Dependency Inversion ได้นั้นเราต้องอาศัย Dependency Injection ดังนั้นแล้วเราควรจะมีความเข้าใจที่ดีต่อคำว่า Dependency Injection ก่อนซึ่งก็สามารถอ่านได้จากลิงค์ด้านล่างเลยครับ

บทความที่จำเป็นต้องอ่านก่อนเริ่ม

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

Dependency Inversion Principle (DIP)

หลัก DIP ถูกกล่าวไว้ว่า

High-level code should not depend on low-level code. Both should depend on abstractions.

Abstractions should not depend upon details. Details should depend upon abstractions.

ซึ่งก็แปลได้ว่า

High-level Code นั้นไม่ควรที่จะผูกติดอยู่กับ Low-level code แต่ทั้งสองนั้นควรจะผูกติดและขึ้นอยู่กับ Abstraction

และ Abstraction นั้นก็ไม่ควรผูกติดอยู่กับรายละเอียดการทำงาน แต่ในทางกลับกันรายละเอียดการทำงานควรจะผูกติดอยู่กับ Abstraction

ลองมาเจาะลึกถึงแต่ละคำกันครับ

Low-level code & High-level code

Low-level code ตามความหมายแล้วก็คือโค้ดที่เกี่ยวกับการทำงานพื้นฐานต่างๆ เช่น การอ่าน/เขียน ไฟล์ หรือ การอ่าน/เขียนข้อมูลจาก Database

High-level code นั้นก็คือโค้ดที่มีความซับซ้อนสูง(กว่า) Low-level code ซึ่งก็อาจจะเป็น โค้ดที่รวมการทำงานของ Low-level code หลายๆ ประเภทเข้าด้วยกันเพื่อให้สามารถทำงานอย่างใดอย่างหนึ่งได้

ลองมาดูตัวอย่างเกี่ยวกับร้านพิซซ่า ( PizzaShop ) กันครับ

จะเห็นว่าในที่นี้ร้านพิซซ่า ( PizzaShop ) เมื่อมี Order เข้ามาร้านพิซซ่านั้นต้องอาศัย พ่อครัว ( PizzaChef ) ในการทำพิซซ่า และ เด็กส่งพิซซ่า ( PizzaBoy ) เพื่อส่งพิซซ่าให้ลูกค้า ดังนั้นจะเห็นได้ว่า

Low-level code ในที่นี้ก็คือคลาสที่ทำงานทั่วๆ ก็คือ PizzaChef  และ PizzaBoy

และสำหรับ High-level code ที่จะควบคุมการทำงานที่ซับซ้อนกว่าก็คือ PizzaShop

Abstraction

Abstraction ความหมายทั่วไปก็คือ “นามธรรม” หรือสิ่งที่เราจับต้องไม่ได้ ดังนั้นในทางโปรแกรมมิ่งก็คือ Abstract Class และ Interface นั่นเองเพราะทั้ง Abstract Class และ Interface นั้นไม่ได้เป็นที่สามารถใช้งานได้จริงเพียงแต่เป็นสิ่งที่ไว้สำหรับวางโครงสร้างพื้นฐานให้ Class นั่นเอง

ดังนั้นถ้าพูดง่ายๆ จากนิยามของ DIP นั้นก็คือ ทั้ง High-level และ Low-level code ควรจะต้องผูกติดอยู่กับ Abstract หรือ Interface นั่นเอง

กลับมาดูคลาส PizzaShop  กันครับ

จะเห็นได้ว่าตอนนี้ PizzaShop  ที่เป็น High-level code นั้นถูกผูกอยู่กับ PizzaChef  และ PizzaBoy  ที่เป็น Low-level code อยู่ ซึ่งไม่เป็นไปตามหลักการของ DIP แต่ก่อนที่เราจะ Refactor คลาสนี้เราลองมาดูกันก่อนว่าการที่ High-level code ผูกติดอยู่กับ Low-level code นั้นไม่ดียังไง

ลองคิดถึงสถานการณ์ที่ว่าร้านพิซซ่า ( PizzaShop ) นั้นมีพ่อครัวอิตาเลียนคนใหม่ ( ItalianChef ) มาแทนที่พ่อครัวพิซซ่าคนเดิม ( PizzaChef )

และเมื่อเราส่งคลาสใหม่เข้าไปก็จะทำให้เกิด Error เนื่องจาก PizzaShop  นั้นถูกผูกติดอยู่กับเฉพาะ (Highly coupled)  PizzaChef  เท่านั้น

ทำให้เราต้องแก้ไขคลาส PizzaShop  เป็น

ถึงจุดนี้คลาส PizzaShop  เราก็ผูกติดอยู่กับ ItalianChef  แทน รวมถึงการที่เราแก้ไขคลาสนั้นก็คือการฝ่าฝืนหลัก Open Closed Principle (OCP) ด้วย ซึ่งหลัก OCP ได้บอกไว้ว่า

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

และในภายหลังถ้าเรามีการเปลี่ยนแปลงคลาสอื่นๆ ไม่ว่าจะเป็นพ่อครัว ( PizzaChef ) หรือ เด็กส่งพิซซ่า ( PizzaBoy ) เราก็จำเป็นที่จะต้องแก้ไขคลาส PizzaShop  อีก ซึ่งก็เป็นการเพิ่มความเสี่ยงให้ตัวโปรแกรมเรามี Error ในทุกๆ ครั้งที่แก้ไขอีกด้วย

เริ่ม Refactor

เราเริ่มด้วยการสร้าง Abstraction ครับ สำหรับผมแล้วหลักการคิดในการสร้าง Abstraction คือ การดูว่าคลาสที่เป็น High-lvel code นั้นสนใจที่จะใช้อะไรรวมถึงจำเป็นที่จะต้องรู้อะไรบ้าง ซึ่งในที่นี้คลาส PizzaShop  สนใจเพียงคนที่จะทำพิซซ่าได้ ( CanMakePizza ) และคนที่จะส่งพิซซ่าได้ ( CanDeliverPizza )

ตามด้วยการทำให้ High-level code ผูกติดอยู่กับ Abstraction

หลังจากนั้นก็ทำให้ Low-level code ผูกติดอยู่กับ Abstraction เช่นกัน

เพียงเท่านี้คลาสของเราก็ทำตามหลักการของ DIP เป็นที่เรียบร้อยแล้ว และถ้าหากเรามีพ่อครัวอิตาเลียนคนใหม่ ( ItalianChef ) สิ่งที่เราต้องทำก็คือการทำคลาสใหม่นี้ผูกติดกับ Abstraction เช่นเดียวกัน

หรือจะเปลี่ยนคลาสเด็กส่งพิซซ่าเป็นไปรษณีย์ไทย  ThailandPost

และเรียกใช้งานโดย

บทสรุป

จะเห็นได้ว่าเมื่อเราทำให้คลาส PizzaShop  ให้เป็นไปตามหลัก DIP แล้ว คลาส PizzaShop  นั้นจะไม่ผูกติดอยู่กับเฉพาะ PizzaChef  และ PizzaBoy  อีกต่อไป ทำให้เมื่อเราต้องการเปลี่ยนพ่อครัวหรือคนส่งพิซซ่าหลังจากนี้ เราก็ไม่จำเป็นที่จะต้องแก้ไขคลาส  PizzaShop  อีกเลย

อ้างอิง

  1. https://laracasts.com/series/solid-principles-in-php/episodes/5
  2. https://leanpub.com/laravel
prapat