Dependency Inversion Principle (DIP)
TL;DR
หลายคนอาจจะเคยได้ยินคำว่า dependency inversion มาแล้วหรือรู้จักว่ามันแปลว่าหลักการพลิก dependency กลับด้าน แต่กลับด้านกับใคร? กลับด้านแล้วยังไง? แล้วทำไมต้องกลับด้าน? ก่อนจะตอบคำถามเหล่านั้นได้เราต้องรู้จักกับคำว่า flow of control ก่อน
ทิศทางของ Flow of Control
Flow of control ก็คือทิศทางการไหลของโปรแกรมเรา เช่นเรามี main method ที่เป็นจุดเริ่มต้นการทำงาน แล้วในนั้นเราก็ไปเรียก method ที่อยู่ใน class อื่น flow of control ก็จะไหลจาก main method ไปทาง class นั้น

Flow of control ไหลจาก Main -> Store
ทีนี้สมมติว่าใน method ที่อยู่ใน class นั้นมีการเรียกใช้งาน class อื่นต่อ เช่นต้องดึงข้อมูลบางอย่างจากฐานข้อมูล flow of control ก็จะไหลจาก class ที่เป็นคนเรียก (caller) ไปหา class ที่ถูกเรียก (callee) ต่อไป

Flow of control ไหลจาก Main -> Store -> BookDatabase
ทิศทางของ Dependency
จากรูปข้างบนถ้าเราดูในแง่ของ dependency บ้างเราก็จะเห็นว่า Main ต้องรู้จัก Store เพื่อจะเรียกใช้งาน showBooks ใน Store ส่วน Store ก็ต้องรู้จัก listAllBooks ที่อยู่ใน BookDatabase แสดงว่าทิศทางของ dependency ก็ไปทางเดียวกับ flow of control หรือเรียกว่า “dependency normal”

ลูกศรเส้นประที่เป็น dependency ชี้ไปทางเดียวกับลูกศรสีแดงที่เป็น flow of control
ถามว่าแล้วชี้จากซ้ายไปขวาแบบนี้ผิดตรงไหน คำตอบก็คือเมื่อ Store ไปรู้จักกับ database แล้วก็มีโอกาสที่ถ้าถ้าวันนึงจะต้องเปลี่ยน database ผลกระทบจะลามมาถึง Store ด้วย และมีข้อเสียอีกอย่างคือทำให้เวลาจะ test ตัว Store แต่ละครั้งต้องสร้าง BookDatabase ขึ้นมาก่อน ต้องต่อ Database จริงก่อน ทำให้ test ยากและเสียเวลา
ในหนังสือ Clean Architecture ของ Robert C. Martin หรือ Uncle Bob ระบุไว้ว่า
The Dependency Inversion Principle (DIP) tells us that the most flexible systems are those in which source code dependencies refer only to abstractions, not to concretions.
หรือแปลเป็นไทยว่าระบบที่ยืดหยุ่น dependencies จะต้องชี้ไปทางที่ abstract ไม่ใช่ชี้ไปทางที่เป็น concrete
ในกรณีนี้ BookDatabase ถือว่ามีความเป็น concrete มากกว่า Store เพราะว่าจะต้องมีรายละเอียดในการต่อกับ database จะต้องสร้างการเชื่อมต่อไปยัง database จะต้องสร้าง query string เพื่อดึงรายชื่อของหนังสือออกมา ฯลฯ แปลว่าจริง ๆ แล้วลูกศร dependency ถือว่าชี้ผิดด้าน
พลังของ Interface
ปัญหานี้แก้ได้ด้วยการใส่ Interface เข้าไปขั้นระหว่าง Store กับ BookDatabase หนึ่งตัว

Store ใช้งาน BookRepository ที่ถูก implement โดย BookDatabase
เมื่อเราใส่ interface เข้าไปหนึ่งตัวตรงกลางแล้วทำให้มีลูกศรเพิ่มขึ้นมาเป็นสองอัน ทำให้เราเลือกได้ว่าจะให้ interface ที่เพิ่มมาอยู่กับข้างไหนโดยขึ้นอยู่กับการจัด package

ถ้าจัด package แบบนี้จะได้ dependency ไปทางเดียวกับ flow of control (Dependency Normal)

ถ้าจัด package แบบนี้จะได้ dependency สวนทางกับ flow of control (Dependency Inverted)
การเลือกทิศทางของ dependency สวนทางกับ flow of control จึงเป็นที่มาของคำว่า Dependency Inversion และเป็นไปตามหลัก DIP ด้วย ข้อดีที่ได้คือถ้าหากเราต้องการเปลี่ยน database ไปต่อกับ MySQL, MSSQL, Oracle, MongoDB, DynamoDB หรือจะเก็บลงไฟล์ CSV หรือจะเก็บไว้ใน memory อย่างเดียวเราก็แค่ไปสร้าง adapter ตัวใหม่ที่ implement BookRepository มาใส่แทนที่ BookDatabase ตัวเดิมโดยที่ business logic ในฝั่ง Store ไม่ต้องรับรู้และไม่ต้องแก้อะไรเลย หรือถ้าเป็นตอนทำ unit test ก็สร้างตัว mock ที่ส่งข้อมูลตามที่ test case กำหนดก็ได้
สรุป
การทำ Dependency Inversion ทำให้เรามีอิสระในการเลือกทิศทางของ dependency ว่าอยากให้ชี้ไปด้านไหนก็ได้โดยไม่จำเป็นจะต้องไปตาม flow of control อีกต่อไป ซึ่งถ้าหากเราเลือกจัดกลุ่มให้ดีว่าอะไรที่เป็นรายละเอียดภายนอกที่มีความเป็น concrete มากจะต้องมาพึ่งพาส่วนที่เป็นหัวใจของโปรแกรมเราที่มีความเป็น abstract มากกว่าก็จะทำให้หัวใจของโปรแกรมเรามีความยืดหยุ่นมากขึ้น ปลอดภัยจากปัจจัยภายนอกที่เราอาจจะไม่สามารถควบคุมได้