إدخال التبعية في Android

تُعد حقن التبعية (DI) تقنية تُستخدم على نطاق واسع في البرمجة المناسبة لتطوير تطبيقات Android. من خلال اتباع مبادئ DI، يمكنك وضع الأساس لبنية جيدة للتطبيق.

يوفر لك تنفيذ طريقة إدخال التبعية المزايا التالية:

  • قابلية إعادة استخدام الرمز البرمجي
  • سهولة إعادة الهيكلة
  • سهولة الاختبار

أساسيات إدخال التبعية

قبل التحدّث عن إدخال الاعتمادية في Android تحديدًا، توفّر هذه الصفحة نظرة عامة أكثر عمومية حول كيفية عمل حقن التبعية.

ما هو حقن التبعية؟

وغالبًا ما تتطلب الصفوف مراجع لفصول أخرى. على سبيل المثال: صف واحد (Car) قد يحتاج إلى مرجع إلى فئة Engine. تسمى هذه الفئات المطلوبة التبعيات، وفي هذا المثال تعتمد الفئة Car على وجود مثيل لفئة Engine لتشغيله.

ويمكن أن تحصل الفئة على كائن تحتاج إليها بثلاث طرق:

  1. تنشئ الفئة التبعية التي تحتاجها. في المثال أعلاه، سينشئ Car ويهيئ مثيله الخاص Engine
  2. اسحَبها من مكان آخر. تتضمن بعض واجهات برمجة تطبيقات Android، مثل Context من الأشخاص وgetSystemService()، يُرجى تنفيذ هذا الإجراء نفسها.
  3. توفيره كمَعلمة يمكن للتطبيق توفير هذه والتبعيات عند إنشاء الفئة أو تمريرها إلى الدوال التي تحتاج إلى كل تبعية. في المثال أعلاه، تُظهر السمة Car إن الدالة الإنشائية ستتلقى Engine كمعلمة.

الخيار الثالث هو إدخال التبعية! من خلال هذا النهج، يمكنك والتبعيات لفئةٍ ما وتقديمها بدلاً من الحصول على فئة يحصل عليها كل مثال.

إليك مثالاً. بدون إدخال الاعتمادية، الذي يمثل Car الذي تنشئ تبعية 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();
    }
}
فئة السيارة بدون إدخال الاعتمادية

هذا ليس مثالاً على إدخال التبعية لأن الفئة Car هي إنشاء Engine الخاصة بها وقد يمثل ذلك مشكلة للأسباب التالية:

  • ترتبط Car بـ Engine بإحكام - ويستخدم مثال Car واحدًا نوع Engine، ولا يمكن بسهولة تجميع أي فئات فرعية أو عمليات تنفيذ بديلة استخدام البيانات المختلفة. إذا كانت Car ستنشئ Engine الخاصة بها، عليك إنشاء Engine. نوعان من Car بدلاً من إعادة استخدام Car نفسه للمحرّكات من النوع Gas وElectric

  • ويزيد الاعتماد على Engine من صعوبة إجراء الاختبارات. يستخدم Car مثيل حقيقي لـ Engine، وبالتالي لن تتمكن من استخدام اختبار مزدوج لتعديل Engine لحالات اختبار مختلفة.

كيف يبدو الرمز عند إدخال التبعية؟ بدلاً من أن يتم تمثيل كل مثيل من 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 واضبطه لإجراء اختبارات مختلفة.

هناك طريقتان رئيسيتان لإدخال التبعية في Android:

  • تسجيلات المنشئين: هذه هي الطريقة الموضحة أعلاه. يمكنك اجتياز تبعيات إحدى الفئات إلى الدالة الإنشائية لها.

  • "إدخال الحقل" (أو Setter Injection). بعض فئات أُطر عمل Android ينشئ النظام مثيلاً مثل الأنشطة والأجزاء، لذا فإن الدالة الإنشائية لا يمكن الحقن. باستخدام إدخال الحقل، يتم إنشاء مثيل للتبعيات بعد إنشاء الصف. ستبدو التعليمة البرمجية كما يلي:

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();
    }
}

إدخال التبعية تلقائيًا

في المثال السابق، قمت بإنشاء التبعيات وتقديمها وإدارتها الفئات المختلفة بنفسك، دون الاعتماد على مكتبة. وهذا ما يسمى إدخال التبعية يدويًا أو إدخال التبعية اليدوية. في Car على سبيل المثال، كان هناك تبعية واحدة فقط، ولكن يمكن أن لمزيد من التبعيات والفئات وجعل حقن التبعيات يدويًا أكثر مملاً. إدخال التبعية اليدوي أيضًا عدة مشكلات:

  • للتطبيقات الكبيرة، نقل جميع التبعيات وربطها بشكل صحيح يمكن أن تتطلب قدرًا كبيرًا من الرموز النموذجية. في قالب متعدد الطبقات هندسة معمارية، ولإنشاء كائن لطبقة عليا، عليك توفير جميع تبعيات الطبقات الموجودة أسفله. كمثال ملموس، لإنشاء قد تحتاج سيارة حقيقية إلى محرك وناقل الحركة وشاحن وأجزاء أخرى؛ بينما يحتاج المحرك إلى أسطوانات وقوابس احتراق.

  • عندما لا تكون قادرًا على إنشاء تبعيات قبل تمريرها - على سبيل المثال عند استخدام عمليات تهيئة كسولة أو تحديد نطاق الكائنات لتدفقات تطبيق — تحتاج إلى كتابة حاوية مخصصة (أو رسم بياني والتبعيات) التي تدير عمر تبعياتك في الذاكرة.

توجد مكتبات تحل هذه المشكلة ببرمجة عملية إنشاء التبعيات وتوفيرها. وتندرج في فئتين:

  • يشير ذلك المصطلح إلى الحلول المستندة إلى الانعكاس التي تربط بين التبعيات في وقت التشغيل.

  • يشير هذا المصطلح إلى الحلول الثابتة التي تُنشئ الرمز البرمجي لربط التبعيات. في وقت التجميع.

Dagger هي مكتبة حقن تبعية شائعة في Java، وKotlin وAndroid التي تحتفظ بها Google. يسهّل تطبيق Dagger استخدام أداة DI. في تطبيقك من خلال إنشاء وإدارة الرسم البياني للتبعيات من أجلك. أُنشأها جون هنتر، الذي كان متخصصًا توفر تبعيات ثابتة ووقت التجميع بشكل كامل تتناول العديد من مشكلات التطوير والأداء للحلول القائمة على الانعكاس مثل Guice.

بدائل لحقن التبعية

وهناك بديل لحقن التبعية وهو استخدام محدد مواقع الخدمات. نمط تصميم محدد موقع الخدمة أيضًا يعمل على تحسين فصل الفئات عن التبعيات الملموسة. تنشئ صفًا المعروف باسم محدد مواقع الخدمات الذي ينشئ التبعيات وتخزنها ثم ويوفر تلك التبعيات عند الطلب.

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();
    }
}

يختلف نمط محدد موقع الخدمة عن إدخال التبعية في الطريقة يتم استهلاك العناصر. باستخدام نمط محدد مواقع الخدمة، تشتمل الفئات التحكم وطلب حقن الكائنات باستخدام حقن التبعية، يتحكم التطبيق في العناصر المطلوبة ويدخلها بشكل استباقي.

مقارنةً بإدخال التبعية:

  • ينتج عن مجموعة التبعيات التي يتطلبها محدد موقع الخدمة تعليمة برمجية لأن جميع الاختبارات يجب أن تتفاعل مع معايير الجودة محدد مواقع الخدمة.

  • يتم ترميز التبعيات في تنفيذ الفئة، وليس في سطح واجهة برمجة التطبيقات. ونتيجةً لذلك، يكون من الصعب معرفة ما يحتاجه الصف من الخارج. ونتيجة لذلك، فإن التغييرات في قد ينتج عن Car أو الاعتماديات المتوفرة في أداة البحث عن أماكن الخدمة بيئة تشغيل أو اختبار من خلال التسبب في إخفاق المراجع.

  • تُعد إدارة عمر الكائنات أكثر صعوبة إذا كنت تريد النطاق أي شيء بخلاف عمر التطبيق بالكامل.

استخدام Hilt في تطبيق Android

الخيار Hilt هو الخيار الذي يُنصح به من خلال Jetpack لحقن التبعية في Android. تحدد Hilt طريقة قياسية للقيام DI في تطبيقك من خلال توفير حاويات لكل فئة Android في لمشروعهم وإدارة دورات حياتهم تلقائيًا نيابة عنك.

تم بناء Hilt في أعلى مكتبة DI الشهيرة. يمكنك استخدام Dagger للاستفادة من صحة وقت التجميع وأداء بيئة التشغيل وقابلية التوسّع و"استوديو Android" والدعم الذي يقدمه Dagger.

لمعرفة المزيد من المعلومات عن Hilt، يمكنك الاطّلاع على Dependency Injection مع Hilt.

الخاتمة

يوفّر إدخال التبعية لتطبيقك المزايا التالية:

  • قابلية إعادة استخدام الفئات وفصل التبعيات: من الأسهل تبديل وتنفيذ التبعية. تحسين إعادة استخدام الرمز بسبب العكس من السيطرة، ولم تعد الفئات تتحكم في كيفية إنشاء تبعياتها، ولكن بدلاً من ذلك، يمكنك استخدام أي تهيئة.

  • سهولة إعادة الهيكلة: تصبح التبعيات جزءًا يمكن التحقق منه من واجهة برمجة التطبيقات بحيث يمكن التحقق منها في وقت إنشاء الكائن أو في وقت التجميع بدلاً من أن يتم إخفاؤها كتفاصيل للتنفيذ.

  • سهولة الاختبار: لا يدير الصف تبعياته، لذلك عندما تكون لاختباره، يمكنك اجتياز عمليات تنفيذ مختلفة لاختبار جميع حالاتك المختلفة.

لتفهم فوائد حقن التبعية بشكل كامل، يجب أن تجربها يدويًا في تطبيقك كما هو موضّح في مقالة إدخال الاعتمادية اليدوية.

مصادر إضافية

لمعرفة المزيد حول حقن التبعية، يمكنك الاطلاع على الموارد الإضافية التالية.

نماذج