แฉหมดเปลือก! Dependency Injection ไม่ได้ยาก แค่ Dagger แม่งงง

DI คืออะไร แล้วจะฉีดไปทำไม

Travis P
Black Lens
3 min readJul 22, 2018

--

https://m.post.naver.com/viewer/postView.nhn?volumeNo=4761656&memberNo=6147148

เรื่อง DI นี่ผมเล็งจะเขียนนานละ แต่มันเป็นเรื่องที่คลุมเครือมากๆ คือตอนแรกที่ผมหัดใช้ Dagger ก็แค่เปิด doc ทำตาม ลองผิดลองถูกจนสำเร็จใช้งานได้ แต่ไม่ได้เข้าใจ มันงงอะ จะต้มกาแฟทีนี่ต้องประกอบเครื่องทำกาแฟ มี pump มี heater มี thermosiphon อะไรก็ไม่รู้ บาริสต้าก็ไม่ใช่ ไม่รู้เรื่องเว่ย

เมื่อเวลาผ่านไป ผมได้เขียน Angular โดนบังคับให้ใช้ DI และได้ลอง Spring Boot ซึ่งมีพื้นฐานอยู่บน Spring Framework ที่เป็น dependency injection framework โดยกำเนิด ทีนี้พอผมได้เห็นการใช้งาน DI 3 แบบ ก็เริ่มมองเห็น pattern บางอย่าง เริ่มจับใจความได้จนออกมาเป็นโพสนี้ในที่สุด

Dependency คืออะไร

อุปสรรคแรกที่ทำให้ผมไม่เข้าใจ DI คือศัพท์คำนี้ เมื่อก่อนผมไม่เข้าใจว่ามันคืออะไร หลังจากไปเปิด google translate พบว่า

depend = พึ่งพา, อาศัย, ขึ้นอยู่กับ

dependency คือ “โค้ด” ที่โค้ดของเราต้องใช้ หรือ “lib” ที่แอพเราต้องใช้ ยกตัวอย่างเช่น

อย่างนี้คือ Car depends on Engine หรือ Engine เป็น dependency ของ Car นั่นเอง ไม่งั้นก็ start() ไม่ได้

อย่างนี้คือแอพของเรา depends on gson หรือ gson เป็น dependency ของแอพเรานั่นเอง ไม่งั้นก็ parse json ไม่ได้

https://www.instagram.com/music.bnk48official/

หรือกรณีนี้ ผม depends on Music หรือ Music เป็น dependency ของผมนั่นเอง ไม่งั้นก็มีชีวิตอยู่ไม่ได้

Dependency ในบริบทของ DI จะเป็นลักษณะ object หนึ่ง พึ่งพาอีก object หนึ่งแบบ Car พึ่งพา Engine ครับ

Dependency Injection คืออะไร

จากตัวอย่าง Car + Engine ข้างต้น จะเห็นว่า Car เป็นคน new object engine ขึ้นมาเองไม่จัดเป็นการ inject
การ inject คือการที่เราขอ dependency มาใช้​ โดยไม่ได้สร้างเอง

ใช่แล้วครับ แค่การส่งค่าผ่าน constructor ก็ถือเป็น DI แล้ว Car ไม่ได้สร้าง Engine เอง แต่ขอมาใช้ผ่าน constructor เค้าเรียกว่า constructor injection นอกจากนั้นยังมี method inject และ field injection อีก ไปศึกษาต่อได้

ทำไมต้อง Dependency Injection

ตอนแรกที่ผมหัดใช้ Dagger ผมหัดเสร็จก็เลิก ไม่ได้ใช้จริง รู้สึกมันเป็น boilerplate code ที่ไม่จำเป็น แต่วันนี้กลายเป็นว่าขาด DI จะรู้สึกแปลกๆ จุดเปลี่ยนก็คือ

การเขียน Test

ผมขอยกคุณสมบัติของโค้ดที่เทสง่ายมา 2 ประการคือ

  1. จัดฉากได้ สมมติผมเขียน MVP ผมจะเทส presenter ว่าถ้ายิง api fail จะแสดง dialog error ถูกมั้ย ผมต้องบังคับ(จัดฉาก)ให้การยิง api มัน fail ได้
  2. วัดผลได้ ทีนี้พอผมบังคับให้ยิง api fail ได้แล้ว ก็ต้องมีวิธีวัดว่า dialog แสดงจริงมั้ย

จากโค้ดด้านบน ถ้า presenter new Api ขึ้นมาเอง จะไม่สามารถเอาของปลอมเข้าไปแทนเพื่อจัดฉากได้ และถ้า view ไม่ได้ถูก inject เข้ามา ก็ไม่สามารถ verify(view) ได้เช่นกัน

Loose Coupling

เค้าว่ากันว่าให้ออกแบบโค้ดให้ low couple high cohesion ใช่มั้ยครับ วิธีนึงที่ลด coupling ได้คือการใช้ interface แทน class

อย่างนี้ไม่ต่ำเท่าไร เพราะ MyActivity รู้จัก class MyPresenterImpl โดยตรง

อย่างนี้ต่ำลงเพราะ MyActivity รู้จักแค่ interface MyPresenter จะเปลี่ยนจะแก้อะไร ขอให้ implement ตาม interface ก็พอ

แต่ก็ยังตกม้าตายตอนจบอยู่ดีเพราะความจริง MyActivity รู้จัก MyPresenterImpl ตอน new ขึ้นมาใหม่นั่นแหละ

ถ้าเราผลักภาระการสร้างออบเจ็คใหม่ไปที่ DI framework MyActivity ของเราก็จะรู้จักแค่ interface MyPresenter จริงๆ

และยังมีเหตุผลอื่นๆที่เราควรใช้ DI อีก ตามไปอ่านตรงนี้ได้เลย

ส่วนประกอบของ Dependency Injection Framework

เท่าที่ผมสังเกต การใช้งาน DI framework โดยทั่วไปประกอบด้วย 3 ขั้นตอน รายละเอียดจะแตกต่างกันไปตามแต่ละ framework

  1. ขอ dependency
  2. ให้ dependency
  3. สร้าง dependency graph

1. ขอ dependency

ขั้นตอนนี้ง่ายสุด แค่ประกาศว่าคลาสเราจะใช้ออบเจ็คอะไรบ้าง

เช่น Android + Dagger แค่เรา @Inject ไปแปะหน้า constructor มันก็จะรู้แล้วว่าคลาส MyPresenter ต้องการใช้ Api

การขอยังมีอีกหลายวิธี แต่ไม่ขอลงรายละเอียดนะครับ

2. ให้ dependency

โอเค ทีนี้พอมีคนขอแล้ว เราก็ต้องบอก DI framework ว่า dependency เหล่านั้นสร้างยังไง

แค่เอา @Inject ไปแปะหน้า constructor ก็เป็นการบอก Dagger ให้รู้แล้วว่า ถ้าใครมาขอ Api ให้ new Api ด้วย constructor ตัวนี้นะ

ถ้าเพื่อนๆสังเกตดีๆ จะพบว่าทั้งการขอและการให้ ใช้ @Inject เหมือนกันเลย เดี๋ยวอ่านข้อ 3 แล้วจะอ๋อทันที

การให้ dependency ยังมีอีกหลายวิธี ไม่ขอลงรายละเอียดเช่นกันครับ

3. สร้าง dependency graph เชื่อมระหว่างผู้ขอผู้ให้ทุกคน

ขั้นตอนนี้จะเป็นการรวมผู้ขอและผู้ให้ทุกคน มาวิเคราห์ และผลิตสิ่งที่เรียกว่า dependency graph ออกมา เจ้ากราฟนี้จะเก็บขั้นตอนวิธีการสร้างออบเจ็คทุกชนิดเอาไว้ ไม่ว่าใครขอใช้อะไร DI framework ก็สามารถผลิตให้ได้

ตรงนี้เราไม่ต้องทำอะไร เป็นหน้าที่ของ framework ยกเว้นว่าเราจะ config อะไรเป็นพิเศษตามแต่ละ framework จะให้ทำ

สมมติ MyActivity ขอ MyPresenter จะเกิดเหตุการณ์ดังนี้

  1. Dagger พยายามสร้าง MyPresenter จาก @Inject constructor(val api: Api)
  2. Dagger พบว่าการสร้าง MyPresenter ต้องมี Api ก่อน
  3. Dagger พยายามสร้าง Api จาก @Inject constructor()
  4. Api ไม่มี dependency สร้างได้เลย
  5. นำ Api ที่เพิ่งสร้างไปสร้าง MyPresenter
  6. นำ MyPresenter ไปให้ MyActivity

แต่ละ dependency ก็มีวิธีการสร้างมัน ซึ่งก็อาจจะมี dependency ของมันเองอีก ซึ่งก็อาจจะมี dependency ของมันเองอีก depenception จริงๆ

4. Integrate DI framework/library กับโปรเจ็คของเรา

อ้าว ไหนบอกว่ามี 3 ขั้นไง แล้วอันนี้มาจากไหน คืออันนี้แล้วแต่ว่าเรากำลังเขียนอะไร ถ้าผมทำ Spring Boot หรือ Angular ก็ไม่ต้องทำอะไรเลย DI ถูกฝังอยู่ในตัวแล้ว แต่ถ้าผมทำ Android เนี่ย ก็ต้องมานั่งสร้าง component สร้าง module ไล่ generate subcomponent ของแต่ละ Activity ของแต่ละ Fragment เติม ActivityInjector ใน Application แล้วต้องใส่ AndroidInjection.inject() ใส่ base class ต่างๆอีก

ตรงนี้ผมก็ไม่ได้จำได้หรอกนะ จะทำทีก็เปิดอ่านที ตัวใครตัวมันละกัน ตาม link พวกนี้ไปอ่านกันเอง 55555

--

--

Travis P
Black Lens

Android Developer, Kotlin & Flutter Enthusiast and Gamer