تُعد حقن التبعية (DI) تقنية تُستخدم على نطاق واسع في البرمجة المناسبة لتطوير تطبيقات Android. من خلال اتباع مبادئ DI، يمكنك وضع الأساس لبنية جيدة للتطبيق.
يوفر لك تنفيذ طريقة إدخال التبعية المزايا التالية:
- قابلية إعادة استخدام الرمز البرمجي
- سهولة إعادة الهيكلة
- سهولة الاختبار
أساسيات إدخال التبعية
قبل التحدّث عن إدخال الاعتمادية في Android تحديدًا، توفّر هذه الصفحة نظرة عامة أكثر عمومية حول كيفية عمل حقن التبعية.
ما هو حقن التبعية؟
وغالبًا ما تتطلب الصفوف مراجع لفصول أخرى. على سبيل المثال: صف واحد (Car
)
قد يحتاج إلى مرجع إلى فئة Engine
. تسمى هذه الفئات المطلوبة
التبعيات، وفي هذا المثال تعتمد الفئة Car
على
وجود مثيل لفئة Engine
لتشغيله.
ويمكن أن تحصل الفئة على كائن تحتاج إليها بثلاث طرق:
- تنشئ الفئة التبعية التي تحتاجها. في المثال أعلاه،
سينشئ
Car
ويهيئ مثيله الخاصEngine
- اسحَبها من مكان آخر. تتضمن بعض واجهات برمجة تطبيقات Android، مثل
Context
من الأشخاص وgetSystemService()
، يُرجى تنفيذ هذا الإجراء نفسها. - توفيره كمَعلمة يمكن للتطبيق توفير هذه
والتبعيات عند إنشاء الفئة أو تمريرها إلى الدوال
التي تحتاج إلى كل تبعية. في المثال أعلاه، تُظهر السمة
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.
الخاتمة
يوفّر إدخال التبعية لتطبيقك المزايا التالية:
قابلية إعادة استخدام الفئات وفصل التبعيات: من الأسهل تبديل وتنفيذ التبعية. تحسين إعادة استخدام الرمز بسبب العكس من السيطرة، ولم تعد الفئات تتحكم في كيفية إنشاء تبعياتها، ولكن بدلاً من ذلك، يمكنك استخدام أي تهيئة.
سهولة إعادة الهيكلة: تصبح التبعيات جزءًا يمكن التحقق منه من واجهة برمجة التطبيقات بحيث يمكن التحقق منها في وقت إنشاء الكائن أو في وقت التجميع بدلاً من أن يتم إخفاؤها كتفاصيل للتنفيذ.
سهولة الاختبار: لا يدير الصف تبعياته، لذلك عندما تكون لاختباره، يمكنك اجتياز عمليات تنفيذ مختلفة لاختبار جميع حالاتك المختلفة.
لتفهم فوائد حقن التبعية بشكل كامل، يجب أن تجربها يدويًا في تطبيقك كما هو موضّح في مقالة إدخال الاعتمادية اليدوية.
مصادر إضافية
لمعرفة المزيد حول حقن التبعية، يمكنك الاطلاع على الموارد الإضافية التالية.