
في هذه المقالة ، سأتحدث عن أساسيات حقن التبعية (Eng. Dependency Injection ، DI ) بلغة بسيطة ، وكذلك عن أسباب استخدام هذا النهج. هذه المقالة مخصصة لأولئك الذين لا يعرفون ما هو حقن التبعية ، أو الذين يشكون في الحاجة إلى استخدام هذه التقنية. لذلك دعونا نبدأ.
ما هو الإدمان؟
لنلقِ نظرة على المثال أولاً. لدينا ClassC
و ClassB
و ClassC
كما هو موضح أدناه:
class ClassA { var classB: ClassB } class ClassB { var classC: ClassC } class ClassC { }
يمكنك أن ترى أن الفئة ClassA
تحتوي على مثيل للفئة ClassB
، لذلك يمكننا القول أن الفئة ClassA
تعتمد على الفئة ClassB
. لماذا؟ لأن ClassA
يحتاج ClassB
للعمل بشكل صحيح. يمكننا أن نقول أيضًا أن الفئة ClassB
هي تبعية للفئة ClassA
.
قبل المتابعة ، أريد أن أوضح أن هذه العلاقة جيدة ، لأننا لسنا بحاجة لفئة واحدة للقيام بكل العمل في التطبيق. نحتاج إلى تقسيم المنطق إلى فئات مختلفة ، يكون كل منها مسؤولاً عن وظيفة معينة. وفي هذه الحالة ، ستكون الطبقات قادرة على التفاعل بفعالية.
كيفية العمل مع التبعيات؟
لنلقِ نظرة على ثلاث طرق تُستخدم لتنفيذ مهام حقن التبعية:
الطريقة الأولى: إنشاء تبعيات في فئة تابعة
ببساطة ، يمكننا إنشاء كائنات كلما احتجنا إليها. ألقِ نظرة على المثال التالي:
class ClassA { var classB: ClassB fun someMethodOrConstructor() { classB = ClassB() classB.doSomething() } }
إنه سهل جدا! نخلق فئة عندما نحتاج إليها.
الفوائد
- إنه سهل وبسيط.
- تتحكم الفئة التابعة (
ClassA
في حالتنا) بشكل كامل في كيفية إنشاء التبعيات ومتى.
العيوب
- ترتبط
ClassA
و ClassB
ارتباطًا وثيقًا ببعضهما البعض. لذلك ، كلما احتجنا إلى استخدام ClassA
، ClassA
إلى استخدام ClassB
، وسيكون من المستحيل استبدال ClassB
بشيء آخر . - مع أي تغيير في تهيئة الفئة
ClassB
، ستحتاج إلى ضبط التعليمات البرمجية داخل الفئة ClassA
(وجميع الفئات الأخرى التي تعتمد على ClassB
). هذا يعقد عملية تغيير التبعية. - لا يمكن اختبار
ClassA
. إذا كنت بحاجة إلى اختبار فصل دراسي ، ومع ذلك يعد هذا أحد أهم جوانب تطوير البرامج ، فسيتعين عليك إجراء اختبار وحدة لكل فصل على حدة. هذا يعني أنك إذا كنت ترغب في التحقق من التشغيل الصحيح للفئة ClassA
وإنشاء عدة اختبارات وحدة لاختبارها ، ثم ، كما هو موضح في المثال ، ستقوم على أي حال بإنشاء مثيل لفئة ClassB
، حتى عندما لا يكون ذلك ClassB
اهتمامك. إذا حدث خطأ أثناء الاختبار ، فلن تكون قادرًا على فهم موقعه - في ClassA
أو ClassB
. بعد كل شيء ، هناك احتمال أن يؤدي جزء من التعليمات البرمجية في ClassB
إلى حدوث خطأ ، بينما يعمل ClassA
بشكل صحيح. بمعنى آخر ، اختبار الوحدة غير ممكن لأنه لا يمكن فصل الوحدات (الفئات) عن بعضها البعض. - يجب تكوين
ClassA
بحيث يمكن حقن التبعيات. في مثالنا ، يجب أن يعرف كيفية إنشاء ClassC
واستخدامه لإنشاء ClassB
. سيكون من الأفضل إذا لم يكن يعرف شيئًا عن ذلك. لماذا؟ بسبب مبدأ المسؤولية الفردية .
يجب أن يقوم كل فصل بعمله فقط.
لذلك ، لا نريد أن تكون الطبقات مسؤولة عن أي شيء آخر غير مهامها. تنفيذ التبعيات هو مهمة إضافية وضعناها لهم.
الطريقة الثانية: حقن التبعيات من خلال فئة مخصصة
لذا ، لفهم أن حقن التبعيات داخل فئة تابعة ليست فكرة جيدة ، دعنا نستكشف طريقة بديلة. هنا ، تحدد الفئة التابعة جميع التبعيات التي تحتاجها داخل المنشئ وتسمح لفئة المستخدم بتوفيرها. هل هذا حل لمشكلتنا؟ سنكتشف لاحقا.
ألقِ نظرة على نموذج التعليمة البرمجية أدناه:
class ClassA { var classB: ClassB constructor(classB: ClassB){ this.classB = classB } } class ClassB { var classC: ClassC constructor(classC: ClassC){ this.classC = classC } } class ClassC { constructor(){ } } class UserClass(){ fun doSomething(){ val classC = ClassC(); val classB = ClassB(classC); val classA = ClassA(classB); classA.someMethod(); } } view rawDI Example In Medium -
الآن تحصل ClassA
على جميع التبعيات داخل المُنشئ ويمكنها ببساطة استدعاء أساليب فئة ClassB
دون تهيئة أي شيء.
الفوائد
ClassB
الآن اقتران ClassA
و ClassB
بشكل فضفاض ، ويمكننا استبدال ClassB
دون كسر الشفرة داخل ClassA
. على سبيل المثال ، بدلاً من اجتياز ClassB
يمكننا تمرير AssumeClassB
، وهي فئة فرعية من ClassB
، وسيعمل برنامجنا بشكل صحيح.- يمكن الآن اختبار
ClassA
. عند كتابة اختبار وحدة ، يمكننا إنشاء إصدار خاص بنا من ClassB
(كائن اختبار) وتمريره إلى ClassA
. في حالة حدوث خطأ أثناء اجتياز الاختبار ، نعلم الآن بالتأكيد أن هذا خطأ بالتأكيد في ClassA
. ClassB
تحرير ClassB
من العمل مع التبعيات ويمكنه التركيز على مهامه.
العيوب
- تشبه هذه الطريقة آلية السلسلة ، وعند نقطة ما يجب مقاطعة السلسلة. بمعنى آخر ، يجب على مستخدم فئة
ClassA
معرفة كل شيء عن تهيئة ClassB
، والذي بدوره يتطلب معرفة حول تهيئة ClassC
، إلخ. لذلك ، ترى أن أي تغيير في مُنشئ أي من هذه الفئات يمكن أن يؤدي إلى تغيير في فئة الاستدعاء ، ناهيك عن أنه يمكن أن يحتوي ClassA
على أكثر من مستخدم واحد ، وبالتالي سيتكرر منطق إنشاء الكائنات. - على الرغم من أن التبعيات الخاصة بنا واضحة وسهلة الفهم ، إلا أن كود المستخدم غير تافه ويصعب إدارته. لذلك ، كل شيء ليس بهذه البساطة. بالإضافة إلى ذلك ، ينتهك القانون مبدأ المسؤولية الفردية ، لأنه مسؤول ليس فقط عن عمله ، ولكن أيضًا عن تنفيذ التبعيات في الطبقات التابعة.
من الواضح أن الطريقة الثانية تعمل بشكل أفضل من الطريقة الأولى ، لكنها لا تزال بها عيوبها. هل من الممكن إيجاد حل أكثر ملاءمة؟ قبل التفكير في الطريقة الثالثة ، دعنا نتحدث أولاً عن مفهوم حقن التبعية.
ما هو حقن التبعية؟
يعد حقن التبعية طريقة للتعامل مع التبعيات خارج الفئة التابعة عندما لا تحتاج الفئة التابعة إلى فعل أي شيء.
بناءً على هذا التعريف ، من الواضح أن حلنا الأول لا يستخدم فكرة حقن التبعية ، والطريقة الثانية هي أن الطبقة التابعة لا تفعل شيئًا لتوفير التبعيات. لكننا ما زلنا نعتقد أن الحل الثاني سيء. لماذا؟
نظرًا لأن تعريف حقن التبعية لا يقول شيئًا عن المكان الذي يجب أن يحدث فيه العمل مع التبعيات (باستثناء خارج الفئة التابعة) ، يجب على المطور اختيار مكان مناسب لحقن التبعية. كما ترون من المثال الثاني ، فئة المستخدم ليست المكان المناسب تمامًا.
كيف نفعل أفضل؟ لنلقِ نظرة على الطريقة الثالثة للتعامل مع التبعيات.
الطريقة الثالثة: اسمح لشخص آخر بمعالجة التبعيات بدلاً منا
وفقًا للطريقة الأولى ، تكون الفئات التابعة مسؤولة عن الحصول على التبعيات الخاصة بها ، وفي الطريقة الثانية ، نقلنا معالجة التبعيات من الفئة التابعة إلى فئة المستخدم. دعنا نتخيل أن هناك شخصًا آخر يمكنه التعامل مع التبعيات ، ونتيجة لذلك لا تقوم الطبقات التابعة أو فئات المستخدمين بالمهمة. تتيح لك هذه الطريقة التعامل مع التبعيات في التطبيق مباشرة.
تطبيق "نظيف" لحقن التبعية (في رأيي الشخصي)
تقع مسؤولية معالجة التبعيات على عاتق جهة خارجية ، لذلك لن يتفاعل أي جزء من التطبيق معهم.
حقن التبعية ليس تقنية أو إطار عمل أو مكتبة أو شيء من هذا القبيل. هذه مجرد فكرة. الفكرة هي العمل مع التبعيات خارج الطبقة التابعة (يفضل في جزء مخصص بشكل خاص). يمكنك تطبيق هذه الفكرة دون استخدام أي مكتبات أو أطر عمل. ومع ذلك ، فإننا ننتقل عادة إلى أطر عمل لتطبيق التبعيات ، لأنه يبسط العمل ويتجنب كتابة رمز القالب.
أي إطار حقن التبعية لديه اثنين من الخصائص الكامنة. قد تتوفر لك وظائف إضافية أخرى ، لكن هاتين الوظيفتين ستكونان دائمًا موجودة:
أولاً ، توفر هذه الأطر طريقة لتحديد الحقول (الكائنات) التي يجب تنفيذها. تقوم بعض الأُطُر بذلك عن طريق التعليق التوضيحي على حقل أو مُنشئ باستخدام التعليقات التوضيحية @Inject
، ولكن هناك طرقًا أخرى. على سبيل المثال ، يستخدم Koin ميزات اللغة المضمنة في Kotlin لتحديد التنفيذ. Inject
يعني أنه يجب معالجة التبعية بواسطة إطار DI. سيبدو الرمز كالتالي:
class ClassA { var classB: ClassB @Inject constructor(classB: ClassB){ this.classB = classB } } class ClassB { var classC: ClassC @Inject constructor(classC: ClassC){ this.classC = classC } } class ClassC { @Inject constructor(){ } }
ثانياً ، تسمح لك الأطر بتحديد كيفية توفير كل تبعية ، ويحدث ذلك في ملف (ملفات) منفصل. يبدو الأمر كهذا تقريبًا (ضع في اعتبارك أن هذا مجرد مثال ، وقد يختلف من إطار إلى إطار):
class OurThirdPartyGuy { fun provideClassC(){ return ClassC()
لذلك ، كما ترون ، كل وظيفة مسؤولة عن معالجة تبعية واحدة. لذلك ، إذا احتجنا إلى استخدام ClassA
في مكان ما في التطبيق ، فسيحدث ما يلي: ينشئ إطار عمل DI الخاص بنا ClassC
واحدًا من فئة ClassC
عن طريق الاتصال بـ provideClassC
، بتمريره إلى provideClassB
وتلقي مثيل من ClassB
، والذي يتم تمريره إلى provideClassA
، ونتيجة لذلك ، يتم إنشاء provideClassA
. هذا هو السحر تقريبا. الآن دعونا نتفحص مزايا ومزايا الطريقة الثالثة.
الفوائد
- كل شيء بسيط قدر الإمكان. كلا من الطبقة التابعة والفئة التي توفر التبعيات واضحة وبسيطة.
- يتم الجمع بين الفصول بشكل فضفاض ويمكن استبدالها بسهولة من قبل الفئات الأخرى. افترض أننا نريد استبدال
ClassC
بـ AssumeClassC
، وهي فئة فرعية من ClassC
. للقيام بذلك ، تحتاج فقط إلى تغيير رمز الموفر كما يلي ، وحيثما يتم استخدام ClassC
، سيتم الآن استخدام الإصدار الجديد تلقائيًا:
fun provideClassC(){ return AssumeClassC() }
يرجى ملاحظة أن أي رمز داخل التطبيق يتغير ، فقط طريقة مزود. يبدو أنه لا يوجد شيء يمكن أن يكون أكثر بساطة ومرونة.
- لا يصدق اختبار. يمكنك بسهولة استبدال التبعيات بنسخ الاختبار أثناء الاختبار. في الواقع ، يعد حقن التبعية هو المساعد الرئيسي عندما يتعلق الأمر بالاختبار.
- تحسين هيكل الكود ، كما التطبيق يحتوي على مكان منفصل لمعالجة التبعية. نتيجة لذلك ، يمكن لبقية التطبيق التركيز بشكل حصري على وظائفه وعدم التداخل مع التبعيات.
العيوب
- تحتوي أطر عمل DI على عتبة دخول معينة ، لذلك يحتاج فريق المشروع إلى قضاء بعض الوقت ودراسته قبل استخدامه بفعالية.
الخاتمة
- من الممكن التعامل مع التبعية بدون DI ، لكنه قد يؤدي إلى تعطل التطبيق.
- DI هي مجرد فكرة فعالة ، والتي يمكن من خلالها التعامل مع التبعيات خارج الفئة التابعة.
- هو الأكثر فعالية لاستخدام DI في أجزاء معينة من التطبيق. العديد من الأطر تساهم في هذا.
- ليست هناك حاجة إلى الأطر والمكتبات ل DI ، لكنها يمكن أن تساعد كثيرا.
في هذا المقال ، حاولت شرح أساسيات العمل مع مفهوم الحقن التبعي ، وقمت أيضًا بتوضيح أسباب استخدام هذه الفكرة. هناك العديد من الموارد التي يمكنك استكشافها لمعرفة المزيد حول استخدام DI في التطبيقات الخاصة بك. على سبيل المثال ، قسم منفصل في الجزء المتقدم من الدورة التدريبية لمهنة Android خاصتنا مخصص لهذا الموضوع.