على الرغم من حقيقة أن النموذج موجود منذ أكثر من عقد ، إلا أن هناك العديد من المقالات (والترجمات) ، ومع ذلك ، هناك المزيد والمزيد من النزاعات والتعليقات والأسئلة والإنجازات المختلفة.
قبل التاريخفي عام 2004 ، كتب مارتن فاولر المقال الشهير "
انعكاس حاويات التحكم ونمط حقن التبعية " ، الذي وصف النمط أعلاه وتنفيذه لجافا. منذ ذلك الحين ، أصبح النمط موضع نقاش وتطبيق واسع النطاق. في تطوير الأجهزة المحمولة ، خاصة على نظام iOS ، جاء ذلك مع تأخير كبير. على Habré هناك
ترجمات جيدة لهذه المقالة ، حظاً سعيداً وكارما مشرقة لمؤلفها.
هناك معلومات كافية حتى على المحور ، ولكن حقيقة أنه يتم مناقشتها في كل مكان
كيفية القيام بذلك ، ولكن من الناحية العملية في أي مكان -
لماذا ، ألهمني لكتابة المنشور. هل من الممكن إنشاء بنية جيدة إذا كنت لا تعرف ماهية هذا الغرض وما الذي يجب أن يكون جيدًا؟ يمكن أخذ بعض المبادئ والاتجاهات الواضحة في الاعتبار - سيساعد ذلك في تقليل المشاكل غير المتوقعة ، لكن الفهم أفضل.
حقن التبعية هو نمط تصميم يتم فيه تكوين الحقول أو المعلمات لإنشاء كائن خارجيًا.
مع العلم أن الكثير سيقتصر على قراءة الفقرات الأولى ، قمت بتغيير المقال.
على الرغم من أن مثل هذا التعريف لـ DI موجود في العديد من المصادر - فهو غامض ، لأنه يجعل المستخدم يعتقد أن الحقن هو شيء يحل محل إنشاء / تهيئة الكائنات ، أو على الأقل يشارك بنشاط كبير في هذه العملية. بطبيعة الحال ، لن يحظر أحد القيام بهذا التنفيذ من DI. لكن DI يمكن أن تكون مجمّعًا سلبيًا حول إنشاء كائن يوفر توفير معلمات الإدخال. في مثل هذا التنفيذ ، نحصل على مستوى آخر من التجريد وفصل ممتاز للواجبات: الكائن نفسه مسؤول عن التهيئة ، ويقوم الحقن بتنفيذ تخزين البيانات وتزويدهم بوحدات التطبيق.
الآن عن كل شيء في النظام وبالتفصيل.
سأبدأ بنمط بسيط ولماذا كانت هناك حاجة لأنماط جديدة ولماذا أصبحت بعض الأنماط القديمة محدودة للغاية في نطاقها؟
في رأيي ، تم تقديم الجزء الأكبر من التغييرات من خلال الإدخال الشامل للاختبار الذاتي. بالنسبة لأولئك الذين يكتبون بنشاط autotests ، هذه المقالة واضحة ليوم أبيض ، لا يمكنك قراءة المزيد. فقط لا يمكنك أن تتخيل كم من الناس لا يكتبون لهم. أدرك أن الشركات الصغيرة والشركات الناشئة لا تملك هذه الموارد ، لكن للأسف ، غالبًا ما تواجه الشركات الكبيرة مشكلات ذات أولوية أكبر.
المنطق هنا بسيط جدا. افترض أنك تختبر وظيفة ذات معلمتين
a و
b ، وتتوقع الحصول على النتيجة
x . في مرحلة ما ، لا تتحقق توقعاتك ، حيث تقوم الدالة بإرجاع النتيجة
y ، وبعد قضاء بعض الوقت ، يمكنك العثور على حرف مفرد داخل الوظيفة ، مما يؤدي في بعض الحالات إلى تحويل نتيجة الدالة إلى قيمة مختلفة. وكان هذا المفرد يسمى
إدمان ضمني ، ورفضت بكل الطرق الممكنة استخدامه في مثل هذه الحالات. لسوء الحظ ، لن تطرد الكلمات من الأغنية ، وإلا ستكون أغنية مختلفة تمامًا. لذلك ، نأخذ مفردنا كمتغير إدخال في الوظيفة. الآن لدينا 3 متغيرات الإدخال
a ،
b ،
s . يبدو أن كل شيء واضح: نغير المعايير - نحصل على نتيجة لا لبس فيها.
بينما أنا لن أعطي أمثلة. علاوة على ذلك ، نحن لا نتحدث فقط عن الوظائف داخل الفصل ، بل هي حجة تخطيطية يمكن تطبيقها أيضًا على إنشاء فئة أو وحدة نمطية ، إلخ.
ملاحظات سينغلتونملاحظة 1. إذا نظرًا لانتقادات نمط المفرد ، قررت استبداله ، على سبيل المثال ، بعيوب مستخدم ، ثم فيما يتعلق بهذا الموقف ، فإن نفس التبعية الضمنية تلوح في الأفق.
ملاحظة 2. ليس صحيحًا تمامًا أن نقول إنه فقط بسبب الاختبار التلقائي ، لا يستحق استخدام مفردات داخل جسم الوظيفة. بشكل عام ، من وجهة نظر البرمجة ، ليس صحيحًا تمامًا أنه مع نفس الإدخال ، تؤدي الوظيفة إلى نتائج مختلفة. انها فقط في autotests تلوح في الأفق هذه المشكلة بشكل أكثر وضوحا.
تكملة المثال أعلاه. لديك كائن يحتوي على 9 إعدادات مستخدم (متغيرات) ، على سبيل المثال حقوق قراءة / تحرير / تسجيل / طباعة / إعادة توجيه / حذف / قفل / تنفيذ / نسخ مستند. تستخدم وظيفتك ثلاثة متغيرات فقط من هذه الإعدادات. ما الذي يمكنك تمريره إلى الوظيفة: الكائن بالكامل مع 9 متغيرات كمعلمة واحدة ، أو ثلاثة إعدادات ضرورية فقط مع ثلاثة معلمات منفصلة؟ في كثير من الأحيان نقوم بتكبير الكائنات المنقولة حتى لا نضع العديد من المعلمات ، أي أننا نختار الخيار الأول. سيتم اعتبار هذه الطريقة نقل
"تبعيات واسعة بشكل غير معقول" . كما خمنت بالفعل ، لأغراض الاختبار الذاتي ، من الأفضل استخدام الخيار الثاني وتمرير المعلمات المستخدمة فقط.
قدمنا 2 استنتاجات:
- يجب أن تتلقى الوظيفة جميع المعلمات اللازمة عند الإدخال
- يجب ألا تتلقى الوظيفة معلمات إدخال غير ضرورية
أردنا الأفضل - ولكن حصلت على وظيفة مع 6 معلمات. افترض أن كل شيء يتم داخل الوظيفة ، لكن يجب أن يتحمل شخص ما مسؤولية توفير معلمات الإدخال لهذه الوظيفة. كما كتبت بالفعل ، منطقي غير واضح. لا أعني مجرد وظيفة فئة عادية ، ولكن لا يعني وظيفة تهيئة / إنشاء وحدة نمطية (vip ، viper ، كائن بيانات ، إلخ). في هذا السياق ، نعيد صياغة السؤال: من الذي يجب عليه توفير معلمات الإدخال لإنشاء الوحدة النمطية؟
سيكون أحد الحلول هو تحويل هذه الحالة إلى وحدة الاستدعاء. ولكن بعد ذلك اتضح أن وحدة الاتصال تحتاج إلى تمرير معايير الطفل. وهذا يستتبع المضاعفات التالية:
أولاً ، قبل ذلك بقليل ، قررنا تجنب "التبعيات الواسعة بشكل غير معقول". ثانياً ، ليس عليك العمل بجد لفهم أنه سيكون هناك الكثير من المعلمات ، وسيكون من الشائع للغاية تحريرها في كل مرة تضيف فيها وحدات فرعية ، بل إنه من المؤلم التفكير في حذف الوحدات التابعة. بالمناسبة ، من المستحيل في بعض التطبيقات إنشاء تسلسل هرمي للوحدات النمطية على الإطلاق: انظر إلى أي شبكة اجتماعية: الملف الشخصي -> الأصدقاء -> الملف الشخصي للصديق -> أصدقاء الأصدقاء ، إلخ. ثالثًا ، يمكن التذكير بمبدأ SOLI
D في هذا الموضوع: "وحدات المستوى الأعلى مستقلة عن الوحدات ذات المستوى الأدنى"
هذا يؤدي إلى فكرة إنشاء / تهيئة الوحدة في هيكل منفصل. ثم حان الوقت لكتابة بضعة أسطر كمثال:
class AccountList { public func showAccountDetail(account: String) { let accountDetail = AccountDetail.make(account: account)
في المثال ، هناك وحدة نمطية لقائمة الحسابات AccountList ، والتي تستدعي الوحدة النمطية للمعلومات التفصيلية على AccountDetail الحساب.
لتهيئة الوحدة النمطية AccountDetail ، هناك حاجة إلى 3 متغيرات. المتغير Account AccountDetail يستقبل من الوحدة الأصل ، المتغيرات إذن 1 ، يتم حقن إذن 2. نظرًا للحقن ، ستبدو مكالمة الوحدة مع تفاصيل الفاتورة:
let accountDetail = AccountDetail.make(account: account)
بدلا من
let accountDetail = AccountDetail(account: account, permission1: p1, permission2: p2)
سيتم إعفاء الوحدة الرئيسية لقائمة الحسابات ، AccountList ، من الالتزام بتمرير المعلمات مع أذونات لا يعرف عنها شيئًا.
لقد قدمت تطبيق الحقن (التجميع) في وظيفة ثابتة في امتداد الفئة. لكن التنفيذ يمكن أن يكون حسب تقديرك.
كما نرى:
- تلقى وحدة المعلمات اللازمة. يمكن اختبار إنشائها وتنفيذها بأمان على جميع مجموعات القيم.
- الوحدات مستقلة ، ليست هناك حاجة لنقل أي شيء للأطفال أو الحد الأدنى الضروري فقط.
- لا تقوم الوحدات بوظيفة توفير البيانات ؛ فهي تستخدم البيانات الجاهزة (p1 ، p2). وبالتالي ، إذا كنت ترغب في تغيير شيء ما في تخزين البيانات أو توفيرها ، فلن تحتاج إلى إجراء تغييرات على الكود الوظيفي للوحدات النمطية (وكذلك الاختبارات التلقائية الخاصة بها) ، لكنك تحتاج فقط إلى تغيير نظام التجميع نفسه ، أو التمديدات مع التجميع.
جوهر حقن التبعية هو بناء مثل هذه العملية التي ، عند استدعاء وحدة نمطية واحدة من وحدة أخرى ، يقوم كائن / آلية مستقلة بنقل (حقن) البيانات إلى الوحدة النمطية المدعوة. بمعنى آخر ، تم تكوين الوحدة النمطية المدعومة من الخارج.
هناك العديد من طرق التكوين:
منشئ حقن ،
حقن الملكية ،
حقن واجهة .
لـ Swift:
البادئ حقن ،
حقن الملكية ،
طريقة الحقن .
الأكثر شيوعا هي حقن المنشئ (التهيئة) والحقن.
هام: في جميع المصادر تقريبًا ، يوصى بأن تكون حقن المنشئ مفضلة. مقارنة حقن المنشئ / الحقن وحقن العقار:
let account = .. let p1 = ... let p2 = ... let accountDetail = AccountDetail(account: account, permission1: p1, permission2: p2)
أفضل من
let accountDetail = AccountDetail() accountDetail.account = .. accountDetail.permission1 = ... accountDetail.permission2 = ...
يبدو أن مزايا الطريقة الأولى واضحة ، لكن لسبب ما يفهم البعض الحقن على أنه تكوين كائن تم إنشاؤه بالفعل واستخدام الطريقة الثانية. أنا مع الطريقة الأولى:
- الخلق من قبل المصمم يضمن كائن صالح ؛
- باستخدام حقن العقار ، ليس من الواضح ما إذا كان من الضروري اختبار تغيير في خاصية ما في أماكن أخرى غير الإنشاء
- باللغات التي تستخدم الخيار ، لتنفيذ حقن العقار ، تحتاج إلى جعل الحقول اختيارية ، أو الخروج بأساليب التهيئة الذكية (لن تنجح دائمًا الطرق البطيئة). الخيار المفرط يضيف كودًا غير ضروري ومجموعات اختبار غير ضرورية.
ومع ذلك ، حتى تخلصنا من بعض التبعيات ، قمنا فقط بنقلها من كتف إلى آخر. السؤال المنطقي هو أين يمكن الحصول على البيانات من التجميع نفسه (اجعل الوظيفة في المثال).
استخدام المفردات في آلية التجميع لم يعد يؤدي إلى المشاكل المذكورة أعلاه مع التبعية الخفية ، لأنه يمكنك اختبار إنشاء وحدات مع أي مجموعة بيانات.
لكننا هنا نواجه ناقصًا آخر من المفرد: سوء التعامل (ربما يمكنك جلب الكثير من الحجج البغيضة ، لكن الكسل). ليس من الجيد أن تقوم بتشتيت مساحة التخزين / المفردة العديدة الخاصة بك في مجموعات ، عن طريق القياس مع أي شخص ، حيث كانت مبعثرة في وحدات وظيفية. ولكن حتى إعادة البناء هذه ستكون بالفعل الخطوة الأولى نحو النظافة ، لأنه بعد ذلك يمكنك استعادة النظام في التجميعات دون التأثير على اختبارات الكود والوحدة.
إذا كنت ترغب في تبسيط البنية بشكل أكبر ، وكذلك اختبار التحولات وأعمال التجميع ، فسيتعين عليك العمل أكثر من ذلك بقليل.
يتيح لنا مفهوم DI تخزين جميع البيانات اللازمة في حاوية. هذا مريح. أولاً ، يمر حفظ البيانات (التسجيل) وتلقيها (حلها) عبر كائن حاوية واحد ؛ وبالتالي ، فمن الأسهل إدارة البيانات واختبارها. ثانياً ، يمكنك مراعاة اعتماد البيانات على بعضها البعض. في العديد من اللغات ، بما في ذلك سريع ، هناك حاويات إدارة التبعية الجاهزة ، وعادة ما تشكل التبعيات شجرة. ما تبقى من إيجابيات وسلبيات لن أدرج ، يمكنك أن تقرأ عنها على الروابط التي نشرتها في بداية المنشور.
إليك ما قد يبدو عليه التجميع الذي يستخدم الحاوية.
import Foundation import Swinject public class Configurator { private static let container = Container() public static func register<T>(name: String, value: T) { container.register(type(of: value), name: name) { _ in value } } public static func resolve<T>(service: T.Type, name: String) -> T? { return container.resolve(service, name: name) } } extension AccountDetail { public static func make(account: String) -> AccountDetail? { if let p1 = Configurator.resolve(service: Bool.self, name: "permission1"), let p2 = Configurator.resolve(service: Bool.self, name: "permission2") { return AccountDetail(account: account, permission1: p1, permission2: p2) } else { return nil } } }
هذا مثال تنفيذ ممكن. يستخدم المثال إطار عمل
Swinject ، الذي وُلد منذ وقت ليس ببعيد. يتيح لك Swinject إنشاء حاوية لإدارة التبعية التلقائية ، كما يسمح لك بإنشاء حاويات لـ Twitter. يمكن العثور على مزيد من المعلومات حول Swinject في الأمثلة على
raywenderlich . يعجبني هذا الموقع حقًا ، لكن هذا المثال ليس هو الأكثر نجاحًا ، لأنه لا ينظر في استخدام الحاوية إلا في الاختبارات التلقائية ، بينما يجب وضع الحاوية في بنية التطبيق. أنت في التعليمات البرمجية الخاصة بك ، يمكنك كتابة حاوية نفسك.
شكرا لكم جميعا على هذا. آمل أنك لم تشعر بالملل من قراءة هذا النص.