การแทรกทรัพยากร Dependency ใน Android

Dependency Injection (DI) เป็นเทคนิคที่ใช้กันอย่างแพร่หลายในการเขียนโปรแกรม เหมาะกับการพัฒนาแอป Android การปฏิบัติตามหลักการของ DI คุณคือ สำหรับสถาปัตยกรรมแอปที่ดี

การใช้การแทรกทรัพยากร Dependency จะมีข้อดีดังนี้

  • การนำโค้ดมาใช้ซ้ำ
  • การเปลี่ยนโครงสร้างภายในโค้ดที่ง่ายดาย
  • ความง่ายในการทดสอบ

พื้นฐานของการแทรกทรัพยากร Dependency

ก่อนที่จะพูดถึงการแทรกทรัพยากร Dependency ใน Android โดยเฉพาะ หน้านี้จะให้ข้อมูลต่อไปนี้ ภาพรวมทั่วไปเกี่ยวกับวิธีการทำงานของการแทรกทรัพยากร Dependency

การแทรกทรัพยากร Dependency คืออะไร

ชั้นเรียนมักต้องมีการอ้างอิงถึงชั้นเรียนอื่น เช่น คลาส Car อาจต้องมีการอ้างอิงถึงชั้นเรียน Engine เราเรียกชั้นเรียนที่จำเป็นเหล่านี้ dependencies และในตัวอย่างนี้ คลาส Car ขึ้นอยู่กับ มีอินสแตนซ์ของคลาส Engine ที่จะเรียกใช้

คลาสได้รับออบเจ็กต์ที่ต้องการ 3 วิธี ดังนี้

  1. ชั้นเรียนจะสร้างทรัพยากร Dependency ที่จำเป็น ในตัวอย่างด้านบน Car จะสร้างและเริ่มต้นอินสแตนซ์ของตัวเอง Engine
  2. ไปรับจากที่อื่น Android API บางรายการ เช่น Context Getter และ getSystemService() ทำงานนี้
  3. ใช้เป็นพารามิเตอร์ แอปสามารถให้สิ่งต่อไปนี้ ทรัพยากร 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

ตัวอย่างนี้ไม่ใช่ตัวอย่างของการแทรกทรัพยากร 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();
    }
}
คลาสรถที่ใช้การแทรกทรัพยากร Dependency

ฟังก์ชัน 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 โปรดดูแหล่งข้อมูลเพิ่มเติมต่อไปนี้

ตัวอย่าง