Dependency Injection (DI) เป็นเทคนิคที่ใช้กันอย่างแพร่หลายในการเขียนโปรแกรม เหมาะกับการพัฒนาแอป Android การปฏิบัติตามหลักการของ DI คุณคือ สำหรับสถาปัตยกรรมแอปที่ดี
การใช้การแทรกทรัพยากร Dependency จะมีข้อดีดังนี้
- การนำโค้ดมาใช้ซ้ำ
- การเปลี่ยนโครงสร้างภายในโค้ดที่ง่ายดาย
- ความง่ายในการทดสอบ
พื้นฐานของการแทรกทรัพยากร Dependency
ก่อนที่จะพูดถึงการแทรกทรัพยากร Dependency ใน Android โดยเฉพาะ หน้านี้จะให้ข้อมูลต่อไปนี้ ภาพรวมทั่วไปเกี่ยวกับวิธีการทำงานของการแทรกทรัพยากร Dependency
การแทรกทรัพยากร Dependency คืออะไร
ชั้นเรียนมักต้องมีการอ้างอิงถึงชั้นเรียนอื่น เช่น คลาส Car
อาจต้องมีการอ้างอิงถึงชั้นเรียน Engine
เราเรียกชั้นเรียนที่จำเป็นเหล่านี้
dependencies และในตัวอย่างนี้ คลาส Car
ขึ้นอยู่กับ
มีอินสแตนซ์ของคลาส Engine
ที่จะเรียกใช้
คลาสได้รับออบเจ็กต์ที่ต้องการ 3 วิธี ดังนี้
- ชั้นเรียนจะสร้างทรัพยากร Dependency ที่จำเป็น ในตัวอย่างด้านบน
Car
จะสร้างและเริ่มต้นอินสแตนซ์ของตัวเองEngine
- ไปรับจากที่อื่น Android API บางรายการ เช่น
Context
Getter และgetSystemService()
ทำงานนี้ - ใช้เป็นพารามิเตอร์ แอปสามารถให้สิ่งต่อไปนี้
ทรัพยากร Dependency เมื่อมีการสร้างคลาสหรือส่งต่อให้กับฟังก์ชัน
ที่ต้องใช้ทรัพยากร Dependency แต่ละรายการ ในตัวอย่างข้างต้น
Car
ตัวสร้างจะได้รับEngine
เป็นพารามิเตอร์
ตัวเลือกที่ 3 คือการแทรกทรัพยากร Dependency ด้วยวิธีการนี้ คุณจะ ทรัพยากร Dependency ของคลาสและนำมาใช้แทนการจัดระดับ เช่น รับโค้ดได้ด้วยตัวเอง
ตัวอย่างมีดังนี้ โดยไม่มีการแทรกทรัพยากร Dependency จะแสดง Car
ที่
สร้างทรัพยากร Dependency ของ Engine
ของตัวเองในโค้ดจะมีลักษณะดังนี้
Kotlin
class Car { private val engine = Engine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
Java
class Car { private Engine engine = new Engine(); public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.start(); } }
ตัวอย่างนี้ไม่ใช่ตัวอย่างของการแทรกทรัพยากร Dependency เนื่องจากคลาส Car
มีการ
การสร้าง Engine
ของตนเอง ซึ่งอาจเป็นปัญหาเนื่องจากสาเหตุต่อไปนี้
Car
และEngine
จับคู่กันอย่างเหนียวแน่น - อินสแตนซ์ของCar
ใช้ ประเภทEngine
และไม่มีคลาสย่อยหรือการติดตั้งใช้งานทางเลือกที่ทำได้ง่าย หากCar
เป็นการสร้างEngine
ของตัวเอง คุณจะต้องสร้างCar
2 ประเภท แทนที่จะใช้Car
เดิมซ้ำสำหรับเครื่องมือประเภทGas
และElectric
การพึ่งพาอย่างสมบูรณ์ใน
Engine
ทำให้การทดสอบยากขึ้นCar
ใช้องค์ประกอบ อินสแตนซ์จริงของEngine
ดังนั้นคุณจึงไม่สามารถใช้ ทดสอบสองเท่าเพื่อแก้ไขEngine
สำหรับกรอบการทดสอบที่แตกต่างกัน
โค้ดที่มีการแทรกทรัพยากร Dependency มีลักษณะอย่างไร แทนแต่ละอินสแตนซ์
ของ Car
ที่สร้างออบเจ็กต์ Engine
ของตัวเองในการเริ่มต้น
ออบเจ็กต์ Engine
เป็นพารามิเตอร์ในตัวสร้าง:
Kotlin
class Car(private val engine: Engine) { fun start() { engine.start() } } fun main(args: Array) { val engine = Engine() val car = Car(engine) car.start() }
Java
class Car { private final Engine engine; public Car(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Engine engine = new Engine(); Car car = new Car(engine); car.start(); } }
ฟังก์ชัน main
ใช้ Car
เนื่องจาก Car
ขึ้นอยู่กับ Engine
แอปจะสร้างองค์ประกอบ
อินสแตนซ์ของ Engine
จากนั้นใช้เพื่อสร้างอินสแตนซ์ของ Car
ประโยชน์ของแนวทางแบบ DI นี้คือ
ความสามารถในการใช้ซ้ำของ
Car
คุณสามารถส่งผ่านการใช้งานEngine
แบบต่างๆ เพื่อCar
ตัวอย่างเช่น คุณอาจกำหนดคลาสย่อยใหม่ของEngine
ที่มีชื่อว่าElectricEngine
ที่คุณต้องการให้Car
ใช้ หากใช้ DI สิ่งที่คุณต้องทำมีเพียง ในอินสแตนซ์ของคลาสย่อยElectricEngine
ที่อัปเดตแล้ว และCar
ยังคงใช้งานได้ โดยไม่ทำการเปลี่ยนแปลงเพิ่มเติมการทดสอบ
Car
ที่ง่ายดาย คุณสามารถผ่านการทดสอบคู่ สถานการณ์ ตัวอย่างเช่น คุณอาจสร้างเลขคู่ทดสอบของEngine
ที่มีชื่อว่าFakeEngine
และกำหนดค่าสำหรับการทดสอบต่างๆ
การแทรกทรัพยากร Dependency ใน Android มี 2 วิธีหลักๆ ดังนี้
การแทรกตัวสร้าง ซึ่งเป็นวิธีการที่อธิบายข้างต้น คุณสอบผ่าน ทรัพยากร Dependency ของคลาสไปยังตัวสร้าง
Field Injection (หรือ Setter Injection) คลาสเฟรมเวิร์กของ Android บางคลาส เช่น กิจกรรมและส่วนย่อยมีการสร้างอินสแตนซ์โดยระบบ ดังนั้น เครื่องมือสร้าง ไม่สามารถฉีดยาได้ เมื่อใช้การแทรกฟิลด์ ระบบจะสร้างอินสแตนซ์ทรัพยากร Dependency หลังจากสร้างชั้นเรียนแล้ว โค้ดจะมีลักษณะดังนี้
Kotlin
class Car { lateinit var engine: Engine fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.engine = Engine() car.start() }
Java
class Car { private Engine engine; public void setEngine(Engine engine) { this.engine = engine; } public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.setEngine(new Engine()); car.start(); } }
การแทรกทรัพยากร Dependency อัตโนมัติ
ในตัวอย่างก่อนหน้านี้ คุณได้สร้าง ระบุ และจัดการการอ้างอิง
ชั้นเรียนต่างๆ ด้วยตนเอง โดยไม่ต้องใช้ไลบรารี ซึ่งเรียกว่า
การแทรก Dependency ด้วยตนเองหรือการแทรกทรัพยากร Dependency ด้วยตนเอง ในCar
เช่น มีทรัพยากร Dependency เพียง 1 รายการ แต่ทรัพยากร Dependency และคลาสมากกว่า
ทำให้การแทรกทรัพยากร Dependency ด้วยตนเองน่าเบื่อมากขึ้น การแทรกทรัพยากร Dependency ด้วยตนเอง
และยังทำให้เกิดปัญหาหลายประการ ดังนี้
สำหรับแอปขนาดใหญ่ ให้ใช้ทรัพยากร Dependency ทั้งหมดและเชื่อมต่อแอปเหล่านั้น อย่างถูกต้องอาจต้องใช้โค้ดต้นแบบจำนวนมากได้ ในหลายเลเยอร์ ในการสร้างอ็อบเจ็กต์สำหรับเลเยอร์บนสุด คุณต้องใส่ ทรัพยากร Dependency ทั้งหมดของเลเยอร์ด้านล่าง ตัวอย่างที่เป็นรูปธรรม ในการสร้าง รถจริง คุณอาจต้องการเครื่องยนต์ ระบบส่งกำลัง โครงรถยนต์ และชิ้นส่วนอื่นๆ และเครื่องยนต์ก็ต้องมีกระบอกสูบและหัวเทียน
เมื่อคุณสร้างทรัพยากร Dependency ก่อนส่งผ่านไม่ได้ - สำหรับ เช่น เมื่อใช้การเริ่มต้นแบบ Lazy หรือกำหนดขอบเขตวัตถุเพื่อให้ไหล คุณต้องเขียนและคงคอนเทนเนอร์ที่กำหนดเอง (หรือกราฟของ ทรัพยากร Dependency) ที่จัดการอายุการใช้งานของทรัพยากร Dependency ในหน่วยความจำ
มีไลบรารีที่แก้ปัญหานี้ด้วยการทำให้กระบวนการ การสร้างและการให้ทรัพยากร Dependency โดยแบ่งออกเป็น 2 หมวดหมู่ ดังนี้
โซลูชันที่อิงตามการสะท้อนที่เชื่อมต่อทรัพยากร Dependency ขณะรันไทม์
โซลูชันแบบคงที่ที่สร้างโค้ดเพื่อเชื่อมต่อทรัพยากร Dependency ในเวลาคอมไพล์
Dagger คือไลบรารีการแทรกทรัพยากร Dependency ยอดนิยมสำหรับ Java Kotlin และ Android ที่ดูแลโดย Google Dagger ช่วยอำนวยความสะดวกในการใช้ DI ในแอปโดยสร้างและจัดการกราฟทรัพยากร Dependency สำหรับคุณ ทั้งนี้ ให้ทรัพยากร Dependency แบบคงที่และเวลาที่คอมไพล์อย่างสมบูรณ์เพื่อจัดการกับ ปัญหาการพัฒนาและประสิทธิภาพของโซลูชันที่อิงตามการสะท้อนความรู้สึก เช่น Guice
ทางเลือกอื่นๆ แทนการแทรกทรัพยากร Dependency
อีกทางเลือกหนึ่งที่ใช้แทนการแทรกทรัพยากร Dependency คือการใช้ Service Locator รูปแบบการออกแบบตัวระบุตำแหน่งบริการ ปรับปรุงการแยกคลาสออกจากทรัพยากร Dependency ที่เป็นรูปธรรม คุณสร้างชั้นเรียน ที่เรียกว่าเครื่องระบุตำแหน่งบริการ ซึ่งจะสร้างและจัดเก็บทรัพยากร Dependency แล้วระบุ แสดงทรัพยากร Dependency ดังกล่าวตามคำขอ
Kotlin
object ServiceLocator { fun getEngine(): Engine = Engine() } class Car { private val engine = ServiceLocator.getEngine() fun start() { engine.start() } } fun main(args: Array) { val car = Car() car.start() }
Java
class ServiceLocator { private static ServiceLocator instance = null; private ServiceLocator() {} public static ServiceLocator getInstance() { if (instance == null) { synchronized(ServiceLocator.class) { instance = new ServiceLocator(); } } return instance; } public Engine getEngine() { return new Engine(); } } class Car { private Engine engine = ServiceLocator.getInstance().getEngine(); public void start() { engine.start(); } } class MyApp { public static void main(String[] args) { Car car = new Car(); car.start(); } }
รูปแบบตัวระบุตำแหน่งบริการจะแตกต่างจากการแทรก Dependency ในลักษณะที่ มีการใช้งานองค์ประกอบ เมื่อใช้รูปแบบตัวระบุตำแหน่งบริการ คลาสจะมี ควบคุมและขอให้วัตถุที่จะฉีด ด้วยการแทรกทรัพยากร Dependency แอปมีการควบคุมและแทรกออบเจ็กต์ที่จำเป็นในเชิงรุก
เมื่อเทียบกับการแทรกทรัพยากร Dependency
คอลเล็กชันของทรัพยากร Dependency ที่ตัวระบุตำแหน่งบริการต้องการจะสร้างโค้ด ที่จะทดสอบได้ยากขึ้น เพราะการทดสอบทั้งหมด จะต้องมีการโต้ตอบกับแบบเดียวกันทั่วโลก Service Locator
ทรัพยากร Dependency จะได้รับการเข้ารหัสในการใช้งานคลาส ไม่ใช่ในแพลตฟอร์ม API ด้วยเหตุนี้ การจะรู้สิ่งที่ชั้นเรียนต้องการจากภายนอกจึงเป็นเรื่องยาก ดังนั้น การเปลี่ยนแปลงจึงเป็น
Car
หรือทรัพยากร Dependency ที่มีในตัวระบุตำแหน่งบริการอาจทำให้เกิดรันไทม์หรือการทดสอบ ล้มเหลวโดยทำให้การอ้างอิงล้มเหลวการจัดการอายุการใช้งานของออบเจ็กต์จะทำได้ยากหากต้องการกำหนดขอบเขต อื่นๆ นอกเหนือจากอายุการใช้งานของทั้งแอป
ใช้ Hilt ในแอป Android
Hilt เป็นอุปกรณ์ที่แนะนำของ Jetpack ไลบรารีสำหรับการแทรกทรัพยากร Dependency ใน Android Hilt กำหนดวิธีการมาตรฐานในการดำเนินการ DI ในแอปพลิเคชันของคุณโดยให้บริการคอนเทนเนอร์สำหรับ Android ทุกคลาสใน และจัดการวงจรให้คุณโดยอัตโนมัติ
Hilt สร้างขึ้นจากไลบรารี DI ยอดนิยม นักวิเคราะห์เพื่อใช้ประโยชน์จาก รวบรวมความถูกต้องของเวลา ประสิทธิภาพของรันไทม์ ความสามารถในการปรับขนาด และ Android Studio ที่ Dagger มอบให้
หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ Hilt โปรดดูที่ Dependency Injection พร้อม Hilt
บทสรุป
Dependency Injection ทำให้แอปมีข้อดีดังต่อไปนี้
การนำคลาสมาใช้ซ้ำและการแยกทรัพยากร Dependency ได้: เปลี่ยนได้ง่ายกว่าเดิม การนำทรัพยากร Dependency ไปใช้ การใช้โค้ดซ้ำมีประสิทธิภาพดีขึ้นเนื่องจากการกลับด้าน ของการควบคุม และคลาสไม่ได้ควบคุมวิธีสร้างทรัพยากร Dependency อีกต่อไป แต่ใช้งานกับการกำหนดค่าใดก็ได้
การเปลี่ยนโครงสร้างภายในโค้ดได้ง่าย: ทรัพยากร Dependency จะกลายเป็นส่วนที่ยืนยันได้ของ API เพื่อให้ตรวจสอบได้ในเวลาที่สร้างออบเจ็กต์หรือในเวลาคอมไพล์ แทนที่จะซ่อนไว้เป็นรายละเอียดการใช้งาน
การทดสอบที่ง่าย: คลาสไม่ได้จัดการการอ้างอิง ดังนั้นเมื่อคุณ คุณสามารถผ่านการใช้งาน แบบต่างๆ เพื่อทดสอบ กรณีต่างๆ ของคุณได้
คุณควรลองใช้เพื่อทำความเข้าใจถึงประโยชน์จากการแทรก Dependency อย่างถ่องแท้ ด้วยตนเองในแอปตามที่แสดงในการแทรกทรัพยากร Dependency ด้วยตนเอง
แหล่งข้อมูลเพิ่มเติม
หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับการแทรกทรัพยากร Dependency โปรดดูแหล่งข้อมูลเพิ่มเติมต่อไปนี้