Dependency Injection (Swift as example)

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


Dependency Injection คืออะไร ?

นิยามอย่างง่ายก็คือ การส่งค่าค่าหนึ่งไปให้อีกออปเจคหนึ่งใช้ ส่วนนิยามที่สอดคล้องกับคอนเซ็ปต์นี้ก็คือ การฉีด Dependency เข้าไปให้ออบเจ็กต์ใดๆ แทนที่จะให้ออบเจ็กต์นั้นทำหน้าที่ในการ ‘instantiate’ Dependency นั้นเอง

Dependency คืออะไร —ขออธิบายโดยการยกตัวอย่างดังนี้
การที่ออบเจ็กต์ A มี Dependency เป็นออบเจ็กต์ B นั้นหมายความว่า การทำงานของ A นั้นขึ้นอยู่กับ B ด้วย นั่นคือ A ต้องพึ่งพาอาศัย B นั่นเอง ^__^
กำหนดค่าให้ StoreManager ได้อย่างไร

จากโค้ดข้างต้น เราสามารถกำหนดค่าให้ storageManager ได้ด้วยวิธีไหนบ้าง?

วิธีที่ 1: ให้ ViewController สร้าง storeManager เอง นั่นหมายความว่า นอกจาก ViewController จะรู้เกี่ยวกับ behavior ของ storeManager แล้ว (เพราะมันต้องใช้ storeManager ในคลาส) มันยังรู้ว่าจะสร้าง storeManager ได้อย่างไรด้วย ดังตัวอย่างข้างล่างนี้

Initialise StoreManager ภายในคลาส

วิธีที่ 2: ให้ inject instance ของ StoreManager ให้กับ ViewController และด้วยวิธีนี้ ViewController ก็ไม่จำเป็นต้องรู้ว่าจะสร้าง storeManager ได้อย่างไร

Inject StoreManager เข้ามาจากภายนอก

ลองมาดูอีกหนึ่งตัวอย่าง ซึ่งอธิบายประโยชน์ของ Dependency Injection ได้ชัดเจนขึ้น จากโค้ดด้านล่าง จะเห็นได้ว่าคลาส User ทำการสร้าง instance ของคลาส InMemoryCache เองโดยที่กำหนดค่าให้กับ instance variable ที่ชื่อ cache

คำถามก็คือ จำเป็นมั้ยที่คลาส User ต้องรู้ว่าจะสร้าง instance ของ InMemoryCache (ซึ่งเป็นคลาสที่ conform โปรโตคอล Cache) ได้อย่างไร ตัวอย่างนี้เขียนแบบวิธีที่ 1 ตามที่อธิบายไปแล้วในตัวอย่างแรก

ลองเปลี่ยนตัวอย่างนี้ให้เขียนแบบวิธีที่ 2 กัน

Inject InMemoryCache เข้ามาจากภายนอก

จากตรงนี้ สังเกตได้ว่าเราสามารถเปลี่ยนชนิดของ Cache ไปเป็นคลาสอื่นนอกเหนือจาก InMemoryCache ได้ตราบใดที่คลาสนั้นๆยัง conform โปรโตคอล Cache


ประโยชน์ของ Dependency Injection

ชัดเจน

การ inject Dependency เข้าไป ทำให้เรามองเห็น Responsibility ของคลาสได้ชัดเจนยิ่งขึ้น จากตัวอย่างข้างต้น จะเห็นได้ว่าคลาส User มัน depend กับ Cache จึงสามารถเดาได้ว่าภายในคลาส​ User น้ันน่าจะมีการใช้งาน Cache (ถึงแม้ว่าเราไม่ได้เข้าไปดูภายในคลาส User ว่าเขียนอะไรยังไง)ในการ store หรือ retreive ข้อมูลของตัวมันเอง เป็นต้น

Test ง่าย

Dependency Injection ทำให้การ test เป็นไปได้ง่ายขึ้น เราสามารถแทนที่ Dependency ด้วยคลาสอื่นๆได้ตามแต่กรณีไป ในตัวอย่างข้างล่างนี้ สามารถแทนที่คลาส InMemoryCache ด้วยคลาส MockCache ซึ่ง conform โปรโตคอล Cache เหมือนกัน ดังนี้

สร้างคลาส mock ขึ้นมาแทนที่คลาสที่ใช้จริงตอน test

Separation of Concerns

จากตัวอย่าง คลาส User ไม่มีความเกี่ยวข้องกับการสร้าง Cache เลย แต่อย่างไรก็ตาม User ยังคงข้องเกี่ยวกับ behavior ของ Cache อยู่ เพราะมันใช้ Cache ภายในคลาสเอง ดังนั้น เราสามารถแยกพิจารณา Responsibility ออกเป็นส่วนๆ ดังนั้นเวลาที่มีการเปลี่ยนแปลงแก้ไขหรือดีบัก ก็จะรู้ว่าควรไปโฟกัสที่ส่วนไหน

Coupling

การใช้ Dependency Injection ร่วมกับโปรโตคอลทำให้ลด Coupling ได้เป็นอย่างดี


Dependency Injection มีกี่ชนิด ?

Initialiser

Inject Dependency ตอน Initialise User

ข้อดีคือ เราสามารถทำให้ Dependency ที่ inject เข้ามานั้น มีคุณสมบัติ immutable (เปลี่ยนแปลงไม่ได้) ทำได้โดยการประกาศ property ที่มารับค่า Dependency ให้เป็น constant นั่นเอง และจากการที่ต้องส่ง Cache เข้ามาระหว่างการทำ initialization ของคลาส User แสดงให้เห็นว่าอะไรคือ Dependency ของคลาส​ User นั่นเอง

Property

Inject Dependency เข้ามาทาง property

วิธีนี้อาจมองว่ามันสะดวกดี กำหนดค่าให้ตอนไหนก็ได้ แต่นั่นหมายความว่า Dependency ที่ถูกส่งเข้ามานั้น สามารถถูกเปลี่ยนแปลงแก้ไขได้ด้วย

Methods

Inject Dependency เข้ามาทาง method

สำหรับวิธีนี้ เราส่ง Dependency เข้ามาทางพารามิเตอร์ของ method ในตัวอย่างข้างต้น cache ไม่ใช่ property ของ User แล้ว


สรุป

เราได้เห็นถึงประโยชน์ของ Dependency Injection กันไปบ้างแล้ว นอกจากจะช่วยบ่งบอกได้ว่าคลาสนั้นๆมี Dependency กับอะไรบ้าง มันยังทำให้ Responsibility ของแต่ละคลาสชัดเจนยิ่งขึ้น โดยเฉพาะในโปรโจคที่มีความซับซ้อน เราจะยิ่งเห็นถึงประโยชน์ของ Dependency Injection ได้เป็นอย่างดี ลองศึกษาและนำไปใช้กันเถอะ ^__^

Reference