سؤال

لقد تم نشر العديد من الأسئلة بالفعل مع أسئلة محددة حول حقن التبعية, ، مثل متى يتم استخدامه وما هي الأطر الموجودة لذلك.لكن،

ما هو حقن التبعية ومتى/لماذا يجب أو لا ينبغي استخدامه؟

هل كانت مفيدة؟

المحلول

حقن التبعية هو تمرير التبعية إلى الآخر أشياء أو نطاق(حاقن التبعية).

حقن التبعية يجعل الاختبار أسهل.يمكن أن يتم الحقن من خلال البناء.

SomeClass() لديه منشئ على النحو التالي:

public SomeClass() {
    myObject = Factory.getObject();
}

مشكلة:لو myObject يتضمن مهام معقدة مثل الوصول إلى القرص أو الوصول إلى الشبكة صعب للقيام باختبار الوحدة على SomeClass().المبرمجين يجب أن يسخروا myObject وقد تقاطع نداء المصنع.

حل بديل:

  • تمرير myObject في كحجة للمنشئ
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

myObject يمكن تمريرها مباشرة مما يجعل الاختبار أسهل.

  • أحد البدائل الشائعة هو تحديد أ لا تفعل شيئا منشئ.يمكن أن يتم حقن التبعية من خلال المستوطنين.(ح / ر @ مايك فيلا).
  • مارتن فاولر يوثق البديل الثالث (ح/ت @MarcDix)، حيث تنفذ الفئات واجهة بشكل صريح بالنسبة للتبعيات التي يرغب المبرمجون في حقنها.

من الصعب عزل المكونات في اختبار الوحدة دون حقن التبعية.

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

نصائح أخرى

أفضل تعريف وجدته حتى الآن هو واحد من جيمس شور:

"حقن التبعية" هو مصطلح 25 دولار لمفهوم 5 سنت....] حقن التبعية يعني إعطاء كائن متغيرات مثيله.[...].

هنالك مقال لمارتن فاولر قد يكون ذلك مفيدًا أيضًا.

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

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

لقد وجدت هذا المثال المضحك من حيث اقتران فضفاض:

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

على سبيل المثال، اعتبر أ Car هدف.

أ Car يعتمد على العجلات والمحرك والوقود والبطارية وما إلى ذلك.يهرب.تقليديًا، نقوم بتعريف العلامة التجارية لهذه الكائنات التابعة جنبًا إلى جنب مع تعريف Car هدف.

بدون حقن التبعية (DI):

class Car{
  private Wheel wh = new NepaliRubberWheel();
  private Battery bt = new ExcideBattery();

  //The rest
}

هنا، Car هدف هو المسؤول عن إنشاء الكائنات التابعة.

ماذا لو أردنا تغيير نوع الكائن التابع له - على سبيل المثال Wheel - بعد الأولي NepaliRubberWheel() ثقوب؟نحن بحاجة إلى إعادة إنشاء كائن السيارة مع تبعيته الجديدة ChineseRubberWheel(), ، ولكن فقط Car الشركة المصنعة يمكن أن تفعل ذلك.

ثم ماذا يفعل Dependency Injection هل لنا...؟

عند استخدام حقن التبعية، يتم إعطاء الكائنات تبعياتها في وقت التشغيل بدلاً من وقت التجميع (وقت تصنيع السيارة).حتى نتمكن الآن من تغيير Wheel متى أردنا.هنا، dependency (wheel) يمكن حقنها Car في وقت التشغيل.

بعد استخدام حقن التبعية:

نحن هنا حقن ال التبعيات (العجلة والبطارية) في وقت التشغيل.ومن هنا جاء المصطلح: حقن التبعية.

class Car{
  private Wheel wh = // Inject an Instance of Wheel (dependency of car) at runtime
  private Battery bt = // Inject an Instance of Battery (dependency of car) at runtime
  Car(Wheel wh,Battery bt) {
      this.wh = wh;
      this.bt = bt;
  }
  //Or we can have setters
  void setWheel(Wheel wh) {
      this.wh = wh;
  }
}

مصدر: فهم حقن التبعية

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

على سبيل المثال، النظر في هذه الفئات:

public class PersonService {
  public void addManager( Person employee, Person newManager ) { ... }
  public void removeManager( Person employee, Person oldManager ) { ... }
  public Group getGroupByManager( Person manager ) { ... }
}

public class GroupMembershipService() {
  public void addPersonToGroup( Person person, Group group ) { ... }
  public void removePersonFromGroup( Person person, Group group ) { ... }
} 

في هذا المثال، تنفيذ PersonService::addManager و PersonService::removeManager سوف تحتاج إلى مثال على GroupMembershipService من أجل القيام بعملها.بدون حقن التبعية، فإن الطريقة التقليدية للقيام بذلك ستكون إنشاء مثيل جديد GroupMembershipService في منشئ PersonService واستخدم سمة المثيل هذه في كلتا الوظيفتين.ومع ذلك، إذا كان منشئ GroupMembershipService يحتوي على العديد من الأشياء التي يتطلبها، أو الأسوأ من ذلك، هناك بعض "أدوات ضبط" التهيئة التي يجب استدعاؤها على GroupMembershipService, ، ينمو الكود بسرعة كبيرة، و PersonService الآن لا يعتمد فقط على GroupMembershipService ولكن أيضًا كل شيء آخر GroupMembershipService يعتمد على.علاوة على ذلك، فإن الارتباط به GroupMembershipService تم ترميزه بشكل ثابت في ملف PersonService مما يعني أنه لا يمكنك "التمثيل" أ GroupMembershipService لأغراض الاختبار، أو لاستخدام نمط الإستراتيجية في أجزاء مختلفة من التطبيق الخاص بك.

باستخدام حقن التبعية، بدلاً من إنشاء مثيل لـ GroupMembershipService داخل الخاص بك PersonService, ، إما أن تقوم بتمريرها إلى PersonService المُنشئ، أو قم بإضافة خاصية (getter وsetter) لتعيين مثيل محلي لها.وهذا يعني أن الخاص بك PersonService لم يعد هناك ما يدعو للقلق بشأن كيفية إنشاء ملف GroupMembershipService, ، فهو يقبل فقط ما يُعطى له، ويعمل معهم.وهذا يعني أيضًا أن أي شيء يمثل فئة فرعية من GroupMembershipService, ، أو ينفذ GroupMembershipService يمكن "حقن" الواجهة في ملف PersonService, ، و ال PersonService لا يحتاج إلى معرفة التغيير.

الإجابة المقبولة هي إجابة جيدة - ولكن أود أن أضيف إلى ذلك أن DI يشبه إلى حد كبير تجنب الثوابت التقليدية في الكود.

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

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

دعونا نجرب مثال بسيط مع سيارة و محرك الطبقات، أي سيارة تحتاج إلى محرك للذهاب إلى أي مكان، على الأقل في الوقت الراهن.أدناه كيف سيبدو الكود بدون حقن التبعية.

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

ولإنشاء فئة Car سنستخدم الكود التالي:

Car car = new Car();

المشكلة في هذا الرمز هي أننا قمنا بربطه بإحكام بـ GasEngine وإذا قررنا تغييره إلى ElectricityEngine فسنحتاج إلى إعادة كتابة فئة السيارة.وكلما زاد حجم التطبيق، زادت المشكلات والصداع الذي سيتعين علينا إضافة واستخدام نوع جديد من المحركات.

بمعنى آخر، مع هذا النهج، تعتمد فئة السيارة عالية المستوى لدينا على فئة GasEngine ذات المستوى الأدنى والتي تنتهك مبدأ انعكاس التبعية (DIP) من SOLID.يقترح DIP أننا يجب أن نعتمد على التجريدات، وليس الطبقات الملموسة.لذا، لتحقيق ذلك، نقدم واجهة IEngine ونعيد كتابة التعليمات البرمجية كما يلي:

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

الآن تعتمد فئة السيارة الخاصة بنا على واجهة IEngine فقط، وليس على تطبيق محدد للمحرك.الآن، الحيلة الوحيدة هي كيف ننشئ نسخة من السيارة ونعطيها فئة محرك ملموسة فعلية مثل GasEngine أو ElectricityEngine.هذا هو المكان حقن التبعية ادخل.

   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

هنا نقوم بشكل أساسي بحقن (تمرير) تبعيتنا (مثيل المحرك) إلى مُنشئ السيارة.إذن، تتمتع فئاتنا الآن بترابط فضفاض بين الكائنات وتبعياتها، ويمكننا بسهولة إضافة أنواع جديدة من المحركات دون تغيير فئة السيارة.

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

هكذا في النهاية حقن التبعية هي مجرد تقنية لتحقيق اقتران فضفاض بين الأشياء وتبعياتها.بدلاً من إنشاء التبعيات مباشرة التي يحتاجها الفصل من أجل تنفيذ تصرفاتها ، يتم توفير التبعيات إلى الفصل (في أغلب الأحيان) عبر حقن المنشئ.

أيضًا عندما يكون لدينا العديد من التبعيات، فمن الممارسات الجيدة جدًا استخدام حاويات Inversion of Control(IoC) التي يمكننا من خلالها تحديد الواجهات التي يجب تعيينها لأي تطبيقات ملموسة لجميع تبعياتنا ويمكننا أن نطلب منها حل تلك التبعيات لنا عند إنشائها كائننا.على سبيل المثال، يمكننا أن نحدد في تعيين حاوية IoC أن محرك يجب أن يتم تعيين التبعية إلى محرك الغاز فئة وعندما نطلب من حاوية IoC الحصول على مثيل لدينا سيارة فئة، فإنه سيتم تلقائيا بناء لدينا سيارة فئة مع أ محرك الغاز مرت التبعية.

تحديث: شاهدت دورة تدريبية حول EF Core من جولي ليرمان مؤخرًا وأعجبتني أيضًا بتعريفها المختصر حول DI.

حقن التبعية هو نمط للسماح لتطبيقك بحقن الكائنات أثناء الطيران إلى الفئات التي تحتاج إليها ، دون إجبار تلك الفئات على أن تكون مسؤولة عن تلك الكائنات.يتيح أن يتم ربط الكود الخاص بك بشكل أكثر فضفاضة ، وتوصيلات CHARTITY CORE CORENT في نظام الخدمات نفسه.

لنتخيل أنك تريد الذهاب لصيد الأسماك:

  • بدون حقن التبعية، عليك أن تعتني بكل شيء بنفسك.أنت بحاجة إلى العثور على قارب، أو شراء صنارة صيد، أو البحث عن الطُعم، وما إلى ذلك.هذا ممكن بالطبع، لكنه يضع الكثير من المسؤولية عليك.من حيث البرمجيات، فهذا يعني أنه يتعين عليك إجراء بحث عن كل هذه الأشياء.

  • مع حقن التبعية، يتولى شخص آخر جميع الاستعدادات ويجعل المعدات المطلوبة متاحة لك.سوف تتلقى ("يتم حقنها") القارب وقضيب الصيد والطعم - كلها جاهزة للاستخدام.

هذا هو التفسير الأكثر بساطة حول حقن التبعية و حاوية حقن التبعية لقد رأيت من أي وقت مضى:

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

  • يحتاج التطبيق إلى Foo (على سبيل المثال.وحدة تحكم)، لذلك:
  • يقوم التطبيق بإنشاء Foo
  • يستدعي التطبيق Foo
    • يحتاج Foo إلى شريط (على سبيل المثال.خدمة)، لذلك:
    • يقوم Foo بإنشاء شريط
    • فو يدعو بار
      • يحتاج Bar إلى BIM (خدمة ، مستودع ، ...) ، إذن:
      • شريط يخلق بيم
      • شريط يفعل شيئا

مع حقن التبعية

  • يحتاج التطبيق إلى Foo، والذي يحتاج إلى Bar، والذي يحتاج إلى Bim، لذلك:
  • يقوم التطبيق بإنشاء Bim
  • يقوم التطبيق بإنشاء شريط ويعطيه Bim
  • ينشئ التطبيق Foo ويعطيه Bar
  • يستدعي التطبيق Foo
    • فو يدعو بار
      • شريط يفعل شيئا

استخدام حاوية حقن التبعية

  • يحتاج التطبيق إلى Foo لذلك:
  • يحصل التطبيق على Foo من الحاوية، لذلك:
    • تقوم الحاوية بإنشاء Bim
    • تقوم الحاوية بإنشاء Bar وتعطيه Bim
    • تقوم الحاوية بإنشاء Foo ومنحها شريطًا
  • يستدعي التطبيق Foo
    • فو يدعو بار
      • شريط يفعل شيئا

حقن التبعية و حاويات حقن التبعية هي أشياء مختلفة:

  • يعد حقن التبعية طريقة لكتابة تعليمات برمجية أفضل
  • تعد حاوية DI أداة للمساعدة في حقن التبعيات

لا تحتاج إلى حاوية للقيام بحقن التبعية.لكن الحاوية يمكن أن تساعدك.

ألا يعني "حقن التبعية" فقط استخدام المنشئات ذات المعلمات والمحددات العامة؟

تعرض مقالة جيمس شور الأمثلة التالية للمقارنة.

منشئ دون حقن التبعية:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example() { 
    myDatabase = new DatabaseThingie(); 
  } 

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
} 

المنشئ مع حقن التبعية:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example(DatabaseThingie useThisDatabaseInstead) { 
    myDatabase = useThisDatabaseInstead; 
  }

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
}

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

وكما قال آخرون، حقن التبعية (DI) يزيل مسؤولية الإنشاء المباشر وإدارة العمر الافتراضي لمثيلات الكائنات الأخرى التي تعتمد عليها فئة اهتماماتنا (فئة المستهلك) (في معنى UML).يتم تمرير هذه المثيلات بدلاً من ذلك إلى فئة المستهلك لدينا، عادةً كمعلمات منشئة أو عبر محددات الخصائص (عادةً ما يتم تنفيذ إدارة مثيلات كائن التبعية وتمريرها إلى فئة المستهلك بواسطة انقلاب التحكم (IoC) الحاوية، ولكن هذا موضوع آخر).

DI، DIP والصلبة

على وجه التحديد، في نموذج روبرت سي مارتن المبادئ الصلبة للتصميم الموجه للكائنات, DI هو أحد التطبيقات الممكنة لـ مبدأ انعكاس التبعية (DIP).ال DIP هو D التابع SOLID تعويذة، شعار - تتضمن تطبيقات DIP الأخرى محدد موقع الخدمة وأنماط المكونات الإضافية.

الهدف من DIP هو فصل التبعيات الضيقة والملموسة بين الفئات، وبدلاً من ذلك، تخفيف الاقتران عن طريق التجريد، والذي يمكن تحقيقه عبر interface, abstract class أو pure virtual class, حسب اللغة والأسلوب المستخدم.

بدون DIP، فإن الكود الخاص بنا (الذي أسميته "فئة الاستهلاك") يقترن بشكل مباشر بتبعية ملموسة وغالبًا ما يكون أيضًا مثقلًا بمسؤولية معرفة كيفية الحصول على مثيل لهذه التبعية وإدارته، أي.من الناحية المفاهيمية:

"I need to create/use a Foo and invoke method `GetBar()`"

وحيث أنه بعد تطبيق DIP، يتم تخفيف المتطلبات، ويصبح الاهتمام بالحصول على عمر الخدمة وإدارته Foo تمت إزالة التبعية:

"I need to invoke something which offers `GetBar()`"

لماذا استخدام DIP (و DI)؟

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

إحدى نتائج DI هي أن إدارة عمر مثيلات كائن التبعية لم تعد تتحكم فيها فئة مستهلكة، حيث يتم الآن تمرير كائن التبعية إلى فئة المستهلك (عبر المُنشئ أو حقن الواضع).

يمكن رؤية ذلك بطرق مختلفة:

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

متى تستخدم دي؟

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

مثال

إليك تطبيق بسيط لـ C#.بالنظر إلى فئة الاستهلاك أدناه:

public class MyLogger
{
   public void LogRecord(string somethingToLog)
   {
      Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
   }
}

على الرغم من أنها تبدو غير ضارة، إلا أنها تحتوي على اثنين static التبعية على فئتين أخريين، System.DateTime و System.Console, ، والتي لا تحد فقط من خيارات إخراج التسجيل (سيكون التسجيل إلى وحدة التحكم عديم القيمة إذا لم يكن هناك أحد يراقب)، ولكن الأسوأ من ذلك، أنه من الصعب الاختبار تلقائيًا نظرًا للاعتماد على ساعة نظام غير حتمية.

ومع ذلك يمكننا التقديم DIP إلى هذه الفئة، من خلال تجريد الاهتمام بالطابع الزمني باعتباره تبعية، واقتران MyLogger فقط إلى واجهة بسيطة:

public interface IClock
{
    DateTime Now { get; }
}

يمكننا أيضًا تخفيف الاعتماد على Console إلى التجريد، مثل أ TextWriter.عادةً ما يتم تنفيذ حقن التبعية على هذا النحو أيضًا constructor الحقن (تمرير تجريد إلى تبعية كمعلمة لمنشئ فئة مستهلكة) أو Setter Injection (تمرير التبعية عبر a setXyz() واضع أو .Net الملكية مع {set;} مُعرف).يُفضل حقن المُنشئ، حيث يضمن ذلك أن يكون الفصل في الحالة الصحيحة بعد الإنشاء، ويسمح بوضع علامة على حقول التبعية الداخلية على أنها readonly (ج#) أو final (جافا).لذلك باستخدام حقن المنشئ في المثال أعلاه، يتركنا هذا مع:

public class MyLogger : ILogger // Others will depend on our logger.
{
    private readonly TextWriter _output;
    private readonly IClock _clock;

    // Dependencies are injected through the constructor
    public MyLogger(TextWriter stream, IClock clock)
    {
        _output = stream;
        _clock = clock;
    }

    public void LogRecord(string somethingToLog)
    {
        // We can now use our dependencies through the abstraction 
        // and without knowledge of the lifespans of the dependencies
        _output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
    }
}

(خرسانة Clock يجب توفيرها، والتي بالطبع يمكن العودة إليها DateTime.Now, ، ويجب توفير التبعيتين بواسطة حاوية IoC عبر حقن المنشئ)

يمكن إنشاء اختبار وحدة آلي، والذي يثبت بشكل قاطع أن المُسجل الخاص بنا يعمل بشكل صحيح، حيث أصبح لدينا الآن سيطرة على التبعيات - الوقت، ويمكننا التجسس على المخرجات المكتوبة:

[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
    // Arrange
    var mockClock = new Mock<IClock>();
    mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
    var fakeConsole = new StringWriter();

    // Act
    new MyLogger(fakeConsole, mockClock.Object)
        .LogRecord("Foo");

    // Assert
    Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}

الخطوات التالية

يرتبط حقن التبعية دائمًا بـ انقلاب حاوية التحكم (IoC), ، لحقن (توفير) مثيلات التبعية الملموسة، وإدارة مثيلات العمر الافتراضي.أثناء عملية التكوين / التمهيد، IoC تسمح الحاويات بتحديد ما يلي:

  • رسم الخرائط بين كل تجريد والتنفيذ الملموس المكوّن (على سبيل المثال. "في أي وقت يطلب المستهلك IBar, ، ارجع أ ConcreteBar مثال")
  • يمكن إعداد السياسات لإدارة عمر كل تبعية، على سبيل المثال.لإنشاء كائن جديد لكل مثيل مستهلك، لمشاركة مثيل تبعية مفردة عبر جميع المستهلكين، لمشاركة نفس مثيل التبعية فقط عبر نفس مؤشر الترابط، وما إلى ذلك.
  • في .Net، تكون حاويات IoC على علم بالبروتوكولات مثل IDisposable وسوف تتحمل مسؤولية Disposing التبعيات بما يتماشى مع إدارة العمر المكونة.

عادةً، بمجرد تكوين / تمهيد حاويات IoC، فإنها تعمل بسلاسة في الخلفية مما يسمح للمبرمج بالتركيز على التعليمات البرمجية الموجودة بدلاً من القلق بشأن التبعيات.

مفتاح التعليمات البرمجية الملائمة لـ DI هو تجنب الاقتران الثابت للفئات، وعدم استخدام new() لإنشاء التبعيات

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

لكن الفوائد كثيرة، خاصة في القدرة على اختبار فئة اهتمامك بدقة.

ملحوظة :إنشاء / رسم الخرائط / الإسقاط (عبر new ..()) من POCO / POJO / تسلسل DTOs / الرسوم البيانية للكيانات / إسقاطات JSON المجهولة وآخرون - أي.فئات أو سجلات "البيانات فقط" - المستخدمة أو التي يتم إرجاعها من الأساليب لا تعتبر تبعيات (بمعنى UML) ولا تخضع لـ DI.استخدام new لعرض هذه الأمور على ما يرام.

لجعل مفهوم حقن التبعية سهل الفهم.لنأخذ مثالاً على زر التبديل لتبديل (تشغيل/إيقاف) المصباح.

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

يحتاج المحول إلى معرفة المصباح الذي أتصل به مسبقًا (التبعية المشفرة).لذا،

التبديل -> المصباح الدائم // المفتاح متصل مباشرة بالمصباح الدائم، ولا يمكن إجراء الاختبار بسهولة

Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

مع حقن التبعية

يعرف Switch فقط أنني بحاجة إلى تشغيل/إيقاف أي لمبة يتم تمريرها إلي.لذا،

التبديل -> Bulb1 أو Bulb2 أو NightBulb (التبعية المحقونة)

Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

تعديل جوامع مثال للتبديل والمصباح:

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`

الهدف الأساسي من حقن التبعية (DI) هو الاحتفاظ بالكود المصدري للتطبيق ينظف و مستقر:

  • ينظف من رمز تهيئة التبعية
  • مستقر بغض النظر عن التبعية المستخدمة

من الناحية العملية، يفصل كل نمط تصميم بين الاهتمامات لجعل التغييرات المستقبلية تؤثر على الحد الأدنى من الملفات.

المجال المحدد لـ DI هو تفويض تكوين التبعية والتهيئة.

مثال:DI مع البرنامج النصي شل

إذا كنت تعمل أحيانًا خارج Java، فتذكر الطريقة source غالبًا ما يستخدم في العديد من لغات البرمجة النصية (Shell، Tcl، وما إلى ذلك، أو حتى import في بايثون يساء استخدامها لهذا الغرض).

اعتبر الأمر بسيطًا dependent.sh النصي:

#!/bin/sh
# Dependent
touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

البرنامج النصي يعتمد:لن يتم تنفيذه بنجاح من تلقاء نفسه (archive_files غير محدد).

أنت تحدد archive_files في archive_files_zip.sh البرنامج النصي للتنفيذ (باستخدام zip في هذه الحالة):

#!/bin/sh
# Dependency
function archive_files {
    zip files.zip "$@"
}

بدلاً من source- تنفيذ البرنامج النصي مباشرة في البرنامج التابع، يمكنك استخدام injector.sh "الحاوية" التي تغطي كلا "المكونات":

#!/bin/sh 
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

ال archive_files الاعتماد كان فقط حقن داخل متكل النصي.

كان من الممكن أن تحقن التبعية التي تنفذ archive_files استخدام tar أو xz.

مثال:إزالة دي

لو dependent.sh يستخدم البرنامج النصي التبعيات مباشرة، سيتم استدعاء النهج بحث التبعية (وهو خلاف حقن التبعية):

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

المشكلة الآن هي أن "المكون" التابع يجب أن يقوم بإجراء التهيئة بنفسه.

كود مصدر "المكون" ليس كذلك ينظف ولا مستقر لأن كل تغييرات في تهيئة التبعيات تتطلب إصدارًا جديدًا لملف التعليمات البرمجية المصدر الخاص بـ "المكونات" أيضًا.

الكلمات الأخيرة

لم يتم التأكيد على DI ونشره إلى حد كبير كما هو الحال في أطر عمل Java.

لكنه نهج عام لتقسيم المخاوف من:

  • طلب تطوير (أعزب دورة حياة إصدار كود المصدر)
  • طلب تعيين (عديد البيئات المستهدفة ذات دورات حياة مستقلة)

باستخدام التكوين فقط مع بحث التبعية لا يساعد لأن عدد معلمات التكوين قد يتغير حسب التبعية (على سبيل المثال.نوع المصادقة الجديد) بالإضافة إلى عدد أنواع التبعيات المدعومة (على سبيل المثال.نوع قاعدة البيانات الجديدة).

جميع الإجابات المذكورة أعلاه جيدة، وهدفي هو شرح المفهوم بطريقة بسيطة حتى يتمكن أي شخص ليس لديه معرفة بالبرمجة من فهم المفهوم أيضًا

يعد حقن التبعية أحد أنماط التصميم التي تساعدنا على إنشاء أنظمة معقدة بطريقة أبسط.

يمكننا أن نرى مجموعة واسعة من تطبيقات هذا النمط في حياتنا اليومية.بعض الأمثلة هي مسجل الشريط، VCD، محرك الأقراص المضغوطة وما إلى ذلك.

Reel-to-reel portable tape recorder, mid-20th century.

الصورة أعلاه هي صورة لجهاز تسجيل محمول من بكرة إلى بكرة، في منتصف القرن العشرين. مصدر.

الغرض الأساسي من جهاز تسجيل الشريط هو تسجيل الصوت أو تشغيله.

أثناء تصميم النظام فإنه يتطلب بكرة لتسجيل أو تشغيل الصوت أو الموسيقى.هناك احتمالان لتصميم هذا النظام

  1. يمكننا وضع البكرة داخل الآلة
  2. يمكننا توفير خطاف للبكرة حيث يمكن وضعها.

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

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

الفوائد الرئيسية التي حققناها باستخدام حقن التبعية.

  • تماسك عالي واقتران فضفاض.
  • إضفاء الطابع الخارجي على التبعية والنظر فقط إلى المسؤولية.
  • جعل الأشياء كمكونات ودمجها لتكوين أنظمة كبيرة ذات قدرات عالية.
  • فهو يساعد على تطوير مكونات عالية الجودة حيث يتم تطويرها بشكل مستقل ويتم اختبارها بشكل صحيح.
  • يساعد على استبدال المكون بآخر في حالة فشل أحدهما.

الآن تشكل هذه المفاهيم أساسًا لأطر عمل معروفة في عالم البرمجة.Spring Angular وما إلى ذلك هي أطر البرامج المعروفة المبنية على الجزء العلوي من هذا المفهوم

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

مثال لحقن التبعية

في السابق كنا نكتب كود مثل هذا

Public MyClass{
 DependentClass dependentObject
 /*
  At somewhere in our code we need to instantiate 
  the object with new operator  inorder to use it or perform some method.
  */ 
  dependentObject= new DependentClass();
  dependentObject.someMethod();
}

مع حقن التبعية، سيقوم حاقن التبعية بإزالة المثيل لنا

Public MyClass{
 /* Dependency injector will instantiate object*/
 DependentClass dependentObject

 /*
  At somewhere in our code we perform some method. 
  The process of  instantiation will be handled by the dependency injector
 */ 

  dependentObject.someMethod();
}

يمكنك أيضا أن تقرأ

الفرق بين انقلاب التحكم وحقن التبعية

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

حقن التبعية (DI) يعني فصل الكائنات التي تعتمد على بعضها البعض.لنفترض أن الكائن A يعتمد على الكائن B، لذا فإن الفكرة هي فصل هذه الكائنات عن بعضها البعض.لا نحتاج إلى ترميز الكائن باستخدام كلمة رئيسية جديدة بدلاً من مشاركة التبعيات مع الكائنات في وقت التشغيل على الرغم من وقت الترجمة.إذا تحدثنا عن

كيف يعمل حقن التبعية في الربيع:

لا نحتاج إلى ترميز الكائن باستخدام كلمة رئيسية جديدة بدلاً من تحديد تبعية الحبة في ملف التكوين.ستكون حاوية الزنبرك مسؤولة عن ربط الجميع.

انقلاب السيطرة (IOC)

IOC هو مفهوم عام ويمكن التعبير عنه بعدة طرق مختلفة وحقن التبعية هو أحد الأمثلة الملموسة على IOC.

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

  1. حقن البناء
  2. حقن سيتر

1.حقن التبعية على أساس المنشئ:

يتم إنجاز DI القائم على المُنشئ عندما تستدعي الحاوية مُنشئ فئة بعدد من الوسائط، يمثل كل منها تبعية لفئة أخرى.

public class Triangle {

private String type;

public String getType(){
    return type;
 }

public Triangle(String type){   //constructor injection
    this.type=type;
 }
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
        <constructor-arg value="20"/>
  </bean>

2.حقن التبعية القائم على الواضع:

يتم تحقيق DI المستند إلى Setter من خلال الحاوية التي تستدعي أساليب الضبط على حبوبك بعد استدعاء مُنشئ بدون وسيطة أو طريقة مصنع ثابتة بدون وسيطة لإنشاء مثيل للفاصوليا الخاصة بك.

public class Triangle{

 private String type;

 public String getType(){
    return type;
  }
 public void setType(String type){          //setter injection
    this.type = type;
  }
 }

<!-- setter injection -->
 <bean id="triangle" class="com.test.dependencyInjection.Triangle">
        <property name="type" value="equivialteral"/>

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

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

DI باختصار، هي تقنية لإزالة مسؤولية إضافية مشتركة (عبء) على المكونات لجلب المكونات التابعة، من خلال توفيرها لها.

تجعلك شركة DI أقرب إلى مبدأ المسؤولية الفردية (SR)، مثل مبدأ surgeon who can concentrate on surgery.

متى تستخدم دي:أوصي باستخدام DI في جميع مشاريع الإنتاج تقريبًا (الصغيرة/الكبيرة)، خاصة في بيئات الأعمال المتغيرة باستمرار :)

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

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

يُعرف "حقن التبعية" (DI) أيضًا باسم "انعكاس التحكم" (IoC)، ويمكن استخدامه كأسلوب لتشجيع هذا الاقتران السائب.

هناك طريقتان أساسيتان لتنفيذ DI:

  1. حقن المنشئ
  2. حقن سيتر

حقن المنشئ

إنها تقنية تمرير تبعيات الكائنات إلى مُنشئها.

لاحظ أن المنشئ يقبل واجهة وليس كائنًا ملموسًا.لاحظ أيضًا أنه يتم طرح استثناء إذا كانت معلمة orderDao فارغة.وهذا يؤكد على أهمية الحصول على تبعية صالحة.حقن المُنشئ هو، في رأيي، الآلية المفضلة لإعطاء كائن ما تبعياته.من الواضح للمطور أثناء استدعاء الكائن ما هي التبعيات التي يجب إعطاؤها لكائن "الشخص" من أجل التنفيذ الصحيح.

حقن سيتر

لكن خذ بعين الاعتبار المثال التالي... لنفترض أن لديك فصلًا يحتوي على عشر طرق ليس لها تبعيات، ولكنك تضيف طريقة جديدة لها تبعية على IDAO.يمكنك تغيير المنشئ لاستخدام Constructor حقن، ولكن هذا قد يجبرك على إجراء تغييرات على جميع استدعاءات المنشئ في كل مكان.وبدلاً من ذلك، يمكنك فقط إضافة مُنشئ جديد يأخذ التبعية، ولكن بعد ذلك كيف يمكن للمطور أن يعرف بسهولة متى يستخدم مُنشئًا واحدًا على الآخر؟أخيرًا، إذا كان إنشاء التبعية مكلفًا للغاية، فلماذا يجب إنشاؤها وتمريرها إلى المُنشئ بينما لا يمكن استخدامها إلا نادرًا؟"حقنة الضبط" هي إحدى تقنيات الحقن المباشر الأخرى التي يمكن استخدامها في مثل هذه المواقف.

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

  1. دعم حقن التبعية دون الحاجة إلى تعديل مُنشئ الفئة القديمة.
  2. السماح بإنشاء موارد أو خدمات باهظة الثمن في وقت متأخر قدر الإمكان وعند الحاجة فقط.

فيما يلي مثال لكيفية ظهور الكود أعلاه:

public class Person {
    public Person() {}

    public IDAO Address {
        set { addressdao = value; }
        get {
            if (addressdao == null)
              throw new MemberAccessException("addressdao" +
                             " has not been initialized");
            return addressdao;
        }
    }

    public Address GetAddress() {
       // ... code that uses the addressdao object
       // to fetch address details from the datasource ...
    }

    // Should not be called directly;
    // use the public property instead
    private IDAO addressdao;

على سبيل المثال، لدينا 2 فئة Client و Service. Client سوف نستخدم Service

public class Service {
    public void doSomeThingInService() {
        // ...
    }
}

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

الطريق 1)

public class Client {
    public void doSomeThingInClient() {
        Service service = new Service();
        service.doSomeThingInService();
    }
}

الطريق 2)

public class Client {
    Service service = new Service();
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

الطريق 3)

public class Client {
    Service service;
    public Client() {
        service = new Service();
    }
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

1) 2) 3) الاستخدام

Client client = new Client();
client.doSomeThingInService();

مزايا

  • بسيط

سلبيات

  • صعب للاختبار Client فصل
  • عندما نتغير Service منشئ، نحن بحاجة إلى تغيير التعليمات البرمجية في كل مكان إنشاء Service هدف

استخدم حقن التبعية

الطريق 1) حقن المنشئ

public class Client {
    Service service;

    Client(Service service) {
        this.service = service;
    }

    // Example Client has 2 dependency 
    // Client(Service service, IDatabas database) {
    //    this.service = service;
    //    this.database = database;
    // }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

استخدام

Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();

الطريق 2) حقن سيتر

public class Client {
    Service service;

    public void setService(Service service) {
        this.service = service;
    }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

استخدام

Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();

الطريق 3) حقن الواجهة

يفحص https://en.wikipedia.org/wiki/Dependency_injection

===

الآن، تم اتباع هذا الرمز بالفعل Dependency Injection وأنه من الأسهل للاختبار Client فصل.
ومع ذلك، مازلنا نستخدم new Service() عدة مرات، وأنها ليست جيدة عند التغيير Service البناء.لمنع ذلك، يمكننا استخدام حاقن DI مثل
1) دليل بسيط Injector

public class Injector {
    public static Service provideService(){
        return new Service();
    }

    public static IDatabase provideDatatBase(){
        return new SqliteDatabase();
    }
    public static ObjectA provideObjectA(){
        return new ObjectA(provideService(...));
    }
}

استخدام

Service service = Injector.provideService();

2) استخدام المكتبة:لأجهزة الأندرويد خنجر2

مزايا

  • جعل الاختبار أسهل
  • عندما تقوم بتغيير Service, ، ما عليك سوى تغييره في فئة Injector
  • إذا كنت تستخدم استخدام Constructor Injection, ، عندما تنظر إلى مُنشئ Client, ، سترى كم من التبعية Client فصل

سلبيات

  • إذا كنت تستخدم استخدام Constructor Injection, ، ال Service يتم إنشاء الكائن عندما Client تم إنشاؤها، في وقت ما نستخدم الوظيفة في Client الطبقة دون استخدام Service هكذا خلقت Service يضيع

تعريف حقن التبعية

https://en.wikipedia.org/wiki/Dependency_injection

التبعية هي كائن يمكن استخدامه (Service)
الحقن هو تمرير التبعية (Service) إلى كائن تابع (Client) من شأنه أن يستخدمه

أعتقد بما أن الجميع قد كتبوا لشركة DI، اسمحوا لي أن أطرح بعض الأسئلة..

  1. عندما يكون لديك تكوين DI حيث سيتم إدخال جميع التطبيقات الفعلية (وليس الواجهات) في فئة (على سبيل المثال، خدمات لوحدة تحكم) لماذا لا يعد هذا نوعًا من الترميز الثابت؟
  2. ماذا لو كنت أرغب في تغيير الكائن في وقت التشغيل؟على سبيل المثال، يقول التكوين الخاص بي بالفعل أنه عندما أقوم بإنشاء مثيل MyController، قم بإدخال FileLogger كـ ILogger.ولكن قد أرغب في حقن DatabaseLogger.
  3. في كل مرة أرغب في تغيير الكائنات التي يحتاجها AClass، أحتاج الآن إلى النظر في مكانين - الفصل نفسه وملف التكوين.كيف يجعل ذلك الحياة أسهل؟
  4. إذا لم يتم حقن خاصية AClass، فهل سيكون من الصعب الاستهزاء بها؟
  5. نعود إلى السؤال الأول.إذا كان استخدام كائن جديد () أمرًا سيئًا، فكيف يمكننا إدخال التنفيذ وليس الواجهة؟أعتقد أن الكثير منكم يقول أننا في الواقع نقوم بإدخال الواجهة ولكن التكوين يجعلك تحدد تنفيذ تلك الواجهة .. وليس في وقت التشغيل ..يتم ترميزه بشكل ثابت أثناء وقت الترجمة.

يعتمد هذا على الإجابة التي نشرهاAdam N.

لماذا لم يعد لدى PersonService ما يدعو للقلق بشأن GroupMembershipService؟لقد ذكرت للتو أن GroupMembership يحتوي على أشياء متعددة (كائنات/خصائص) يعتمد عليها.إذا كانت GService مطلوبة في PService، فستحصل عليها كخاصية.يمكنك الاستهزاء بذلك بغض النظر عما إذا كنت قد حقنته أم لا.المرة الوحيدة التي أرغب في إدخالها فيها هي إذا كان لدى GMService فئات فرعية أكثر تحديدًا، والتي لن تعرفها حتى وقت التشغيل.ثم قد ترغب في حقن الفئة الفرعية.أو إذا كنت تريد استخدام ذلك كنموذج فردي أو نموذج أولي.لنكون صادقين، يحتوي ملف التكوين على كل شيء مضمن فيما يتعلق بالفئة الفرعية للنوع (الواجهة) التي سيتم حقنها أثناء وقت الترجمة.

يحرر

تعليق جميل من خوسيه ماريا أرانز على DI

تعمل DI على زيادة التماسك عن طريق إزالة أي حاجة لتحديد اتجاه التبعية وكتابة أي كود لاصق.

خطأ شنيع.اتجاه التبعيات يكون في شكل XML أو كتعليقات توضيحية، وتتم كتابة تبعياتك كرمز XML وشروحه.XML والتعليقات التوضيحية هي كود المصدر.

تعمل DI على تقليل الاقتران عن طريق جعل جميع مكوناتك معيارية (أي.قابلة للاستبدال) ولها واجهات محددة جيدًا لبعضها البعض.

خطأ شنيع.لا تحتاج إلى إطار عمل DI لإنشاء رمز معياري يعتمد على الواجهات.

حول استبدال:باستخدام أرشيف .properties البسيط جدًا وClass.forName، يمكنك تحديد الفئات التي يمكن تغييرها.إذا كان من الممكن تغيير أي فئة من التعليمات البرمجية الخاصة بك، فإن Java ليست مناسبة لك، فاستخدم لغة البرمجة النصية.بالمناسبة:لا يمكن تغيير التعليقات التوضيحية دون إعادة الترجمة.

في رأيي هناك سبب واحد فقط لأطر عمل DI:تخفيض لوحة المرجل.باستخدام نظام المصنع الجيد، يمكنك القيام بنفس الشيء، بشكل أكثر تحكمًا وقابلية للتنبؤ مثل إطار عمل DI المفضل لديك، وتعد أطر عمل DI بتقليل التعليمات البرمجية (XML والتعليقات التوضيحية هي كود مصدر أيضًا).تكمن المشكلة في أن تقليل لوحة الغلاية هذا يكون حقيقيًا في حالات بسيطة جدًا (مثيل واحد لكل فئة وما شابه ذلك)، وأحيانًا في العالم الحقيقي، لا يكون اختيار كائن الخدمة المناسب سهلاً مثل تعيين فئة إلى كائن فردي.

حقن التبعية يعني وسيلة (في الواقع على أي حال) لجزء واحد من التعليمات البرمجية (على سبيل المثال فئة) للوصول إلى التبعيات (أجزاء أخرى من التعليمات البرمجية، على سبيل المثال فئات أخرى، يعتمد عليها) بطريقة معيارية دون أن يتم ترميزها بشكل ثابت (حتى يمكن تغييرها أو تجاوزها بحرية، أو حتى ليتم تحميلها في وقت آخر، حسب الحاجة)

(ملاحظة، نعم، لقد أصبح اسمًا مبالغًا فيه بقيمة 25 دولارًا لمفهوم بسيط نوعًا ما), ، لي .25 سنتا

أعلم أن هناك بالفعل العديد من الإجابات، لكنني وجدت هذا مفيدًا جدًا: http://tutorials.jenkov.com/dependency-injection/index.html

لا التبعية:

public class MyDao {

  protected DataSource dataSource =
    new DataSourceImpl("driver", "url", "user", "password");

  //data access methods...
  public Person readPerson(int primaryKey) {...}

}

الاعتماد:

public class MyDao {

  protected DataSource dataSource = null;

  public MyDao(String driver, String url, String user, String
 password){
    this.dataSource = new DataSourceImpl(driver, url, user, password);
  }

  //data access methods...
  public Person readPerson(int primaryKey)
  {...}

}

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

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

$foo = Foo->new($bar);

نحن فقط نسمي ذلك تمرير المعلمات إلى المنشئ.لقد كنا نفعل ذلك بانتظام منذ اختراع المنشئات.

يعتبر "حقن التبعية" نوعًا من "انعكاس التحكم" مما يعني إخراج بعض المنطق من المتصل.ليس هذا هو الحال عندما يقوم المتصل بتمرير المعلمات، لذلك إذا كان ذلك هو DI، فلن يعني DI عكس التحكم.

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

gcc -c foo.cpp; gcc -c bar.cpp

قبل القيام ب

gcc foo.o bar.o -o bar

لا يحتاج الشخص الذي يكتب "make bar" إلى معرفة أن هذا الشريط يعتمد على foo.تم حقن التبعية بين "make bar" و gcc.

الغرض الرئيسي من المستوى المتوسط ​​ليس فقط تمرير التبعيات إلى المنشئ، ولكن إدراج جميع التبعيات في مكان واحد فقط, وإخفائها عن المبرمج (عدم جعل المبرمج يوفرها).

عادةً ما يوفر المستوى المتوسط ​​مصانع للكائنات التي تم إنشاؤها، والتي يجب أن توفر دورًا يجب أن يفي به كل نوع كائن مطلوب.وذلك لأنه بامتلاكك مستوى متوسط ​​يخفي تفاصيل البناء، فقد تكبدت بالفعل عقوبة التجريد التي تفرضها المصانع، لذا يمكنك أيضًا استخدام المصانع.

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

لسوء الحظ، الجزء السيئ هو أن بعض الأشخاص افترضوا أنك بحاجة إلى إطار عمل متخصص للقيام بالتعتيم على التبعية وأنك بطريقة ما مبرمج "أقل شأنا" إذا اخترت عدم استخدام إطار عمل معين للقيام بذلك.هناك أسطورة أخرى مزعجة للغاية، يعتقدها الكثيرون، وهي أن حقن التبعية هو الطريقة الوحيدة لتحقيق تشويش التبعية.هذا خطأ واضح وتاريخي بنسبة 100% ولكنك ستواجه صعوبة في إقناع بعض الأشخاص بوجود بدائل لحقن التبعية لمتطلبات تشويش التبعية الخاصة بك.

لقد فهم المبرمجون متطلبات تشويش التبعية لسنوات وتطورت العديد من الحلول البديلة قبل وبعد تصور حقن التبعية.توجد أنماط المصنع ولكن هناك أيضًا العديد من الخيارات التي تستخدم ThreadLocal حيث لا تكون هناك حاجة إلى الحقن في مثيل معين - يتم حقن التبعية بشكل فعال في الخيط الذي يتمتع بميزة إتاحة الكائن (عبر طرق getter الثابتة الملائمة) لـ أي فئة تتطلب ذلك دون الحاجة إلى إضافة تعليقات توضيحية إلى الفئات التي تتطلب ذلك وإعداد "غراء" XML المعقد لتحقيق ذلك.عندما تكون التبعيات الخاصة بك مطلوبة من أجل الاستمرارية (JPA/JDO أو أي شيء آخر)، فإنها تسمح لك بتحقيق "الاستمرارية العابرة للحدود" بشكل أسهل بكثير وباستخدام نماذج المجال وفئات نماذج الأعمال المكونة بالكامل من POJOs (أي.لا يوجد إطار محدد/مقفل في التعليقات التوضيحية).

من الكتاب، 'مطور جافا ذو أسس جيدة:التقنيات الحيوية لـ Java 7 والبرمجة متعددة اللغات

DI هو شكل معين من IOC ، حيث تكون عملية العثور على تبعياتك خارج السيطرة المباشرة على رمز التنفيذ حاليًا.

من كتاب Apress.Spring.Persistence.with.Hibernate.Oct.2010

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

حقن التبعية (DI) هو أحد أنماط التصميم، والذي يستخدم الميزة الأساسية لـ OOP - العلاقة بين كائن واحد وكائن آخر.بينما يرث الميراث كائنًا واحدًا للقيام بكائن آخر أكثر تعقيدًا وتحديدًا، فإن العلاقة أو الارتباط يقوم ببساطة بإنشاء مؤشر إلى كائن آخر من كائن واحد باستخدام السمة.تجتمع قوة DI مع ميزات أخرى لـ OOP مثل الواجهات ورمز الإخفاء.لنفترض أن لدينا عميلًا (مشتركًا) في المكتبة يمكنه استعارة كتاب واحد فقط من أجل البساطة.

واجهة الكتاب:

package com.deepam.hidden;

public interface BookInterface {

public BookInterface setHeight(int height);
public BookInterface setPages(int pages);   
public int getHeight();
public int getPages();  

public String toString();
}

بعد ذلك يمكننا الحصول على العديد من أنواع الكتب؛أحد أنواعه خيال:

package com.deepam.hidden;

public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages

/** constructor */
public FictionBook() {
    // TODO Auto-generated constructor stub
}

@Override
public FictionBook setHeight(int height) {
  this.height = height;
  return this;
}

@Override
public FictionBook setPages(int pages) {
  this.pages = pages;
  return this;      
}

@Override
public int getHeight() {
    // TODO Auto-generated method stub
    return height;
}

@Override
public int getPages() {
    // TODO Auto-generated method stub
    return pages;
}

@Override
public String toString(){
    return ("height: " + height + ", " + "pages: " + pages);
}
}

الآن يمكن للمشترك أن يرتبط بالكتاب:

package com.deepam.hidden;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Subscriber {
BookInterface book;

/** constructor*/
public Subscriber() {
    // TODO Auto-generated constructor stub
}

// injection I
public void setBook(BookInterface book) {
    this.book = book;
}

// injection II
public BookInterface setBook(String bookName) {
    try {
        Class<?> cl = Class.forName(bookName);
        Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
        BookInterface book = (BookInterface) constructor.newInstance();
        //book = (BookInterface) Class.forName(bookName).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return book;
}

public BookInterface getBook() {
  return book;
}

public static void main(String[] args) {

}

}

يمكن إخفاء الفئات الثلاث لتطبيقها الخاص.الآن يمكننا استخدام هذا الرمز لـ DI:

package com.deepam.implement;

import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;

public class CallHiddenImplBook {

public CallHiddenImplBook() {
    // TODO Auto-generated constructor stub
}

public void doIt() {
    Subscriber ab = new Subscriber();

    // injection I
    FictionBook bookI = new FictionBook();
    bookI.setHeight(30); // cm
    bookI.setPages(250);
    ab.setBook(bookI); // inject
    System.out.println("injection I " + ab.getBook().toString());

    // injection II
    FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
    System.out.println("injection II " + ab.getBook().toString());      
}

public static void main(String[] args) {
    CallHiddenImplBook kh = new CallHiddenImplBook();
    kh.doIt();
}
}

هناك العديد من الطرق المختلفة لكيفية استخدام حقن التبعية.من الممكن دمجها مع Singleton، وما إلى ذلك، ولكن في الأساس لا يتم تحقيق الارتباط إلا عن طريق إنشاء سمة من نوع الكائن داخل كائن آخر.الفائدة تكمن فقط في الميزة، ذلك الكود الذي يجب أن نكتبه مرارًا وتكرارًا يتم إعداده وتنفيذه دائمًا لنا في المستقبل.هذا هو السبب وراء ارتباط شركة DI ارتباطًا وثيقًا بانعكاس التحكم (IoC) مما يعني أن برنامجنا يمرر التحكم في وحدة تشغيل أخرى، والتي تقوم بحقن الفاصوليا في الكود الخاص بنا.(كل كائن يمكن حقنه يمكن توقيعه أو اعتباره حبة.) على سبيل المثال في Spring، يتم ذلك عن طريق الإنشاء والتهيئة سياق التطبيق الحاوية، والتي تفعل هذا العمل بالنسبة لنا.نحن ببساطة نقوم بإنشاء السياق في الكود الخاص بنا ونستدعي تهيئة الفول.في تلك اللحظة تم الحقن تلقائيا.

بكلمات بسيطة، يعد حقن التبعية (DI) طريقة لإزالة التبعيات أو الاقتران المحكم بين كائنات مختلفة.يعطي حقن التبعية سلوكًا متماسكًا لكل كائن.

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

يتم تحميل الكائنات مرة واحدة في حاوية Spring ثم نعيد استخدامها عندما نحتاج إليها عن طريق جلب تلك الكائنات من حاوية Spring باستخدام طريقة getBean(String beanName).

حقن التبعية هو قلب المفهوم المتعلق بإطار عمل الربيع. أثناء إنشاء إطار عمل أي مشروع، قد يؤدي الربيع دورًا حيويًا، وهنا يأتي حقن التبعية في الإبريق.

في الواقع، لنفترض أنك قمت بإنشاء فئتين مختلفتين في Java مثل الفئة A والفئة B، وأيًا كانت الوظيفة المتوفرة في الفئة B التي تريد استخدامها في الفئة A، لذلك في ذلك الوقت يمكن استخدام حقن التبعية.حيث يمكنك وضع كائن من فئة في أخرى، بنفس الطريقة التي يمكنك بها إدخال فئة بأكملها في فئة أخرى لتسهيل الوصول إليها.وبهذه الطريقة يمكن التغلب على التبعية.

إن حقن التبعية هو ببساطة لصق فئتين وفي نفس الوقت إبقائهما منفصلين.

يعد حقن التبعية (DI) جزءًا من ممارسة مبدأ انعكاس التبعية (DIP)، والذي يُسمى أيضًا انعكاس التحكم (IoC).تحتاج في الأساس إلى تنفيذ DIP لأنك تريد جعل التعليمات البرمجية الخاصة بك أكثر نمطية وقابلة للاختبار على الوحدة، بدلاً من مجرد نظام واحد متجانس.لذلك تبدأ في تحديد أجزاء من الكود التي يمكن فصلها عن الفصل واستخلاصها بعيدًا.الآن يجب إدخال تنفيذ التجريد من خارج الفصل.عادة يمكن القيام بذلك عن طريق المنشئ.لذلك تقوم بإنشاء مُنشئ يقبل التجريد كمعلمة، وهذا ما يسمى حقن التبعية (عبر المُنشئ).لمزيد من التوضيح حول حاوية DIP وDI وIoC يمكنك القراءة هنا

أود أن أقترح تعريفًا مختلفًا قليلًا وقصيرًا ودقيقًا لماهية حقن التبعية، مع التركيز على الهدف الأساسي، وليس على الوسائل التقنية (متابعةً لـ هنا):

حقن التبعية هو عملية إنشاء الرسم البياني الثابت ، عديمي الجنسية لكائنات الخدمة ، حيث يتم تحديد كل خدمة من خلال تبعياتها.

الكائنات التي نقوم بإنشائها في تطبيقاتنا (بغض النظر عما إذا كنا نستخدم Java أو C# أو أي لغة أخرى موجهة للكائنات) تقع عادةً ضمن إحدى فئتين:"كائنات الخدمة" (الوحدات) عديمة الحالة والثابتة والعالمية، و"كائنات البيانات" ذات الحالة والديناميكية والمحلية.

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

أحد المتطلبات هو أن الخدمات يجب أن تكون محددة حسب تبعياتها.ما يعنيه هذا يعتمد بالضبط على اللغة والنهج المتبع في نظام معين.عادةً ما يأخذ هذا شكل معلمات المُنشئ، ولكن استخدام أدوات الضبط يعد أيضًا خيارًا.وهذا يعني أيضًا أن تبعيات الخدمة مخفية (عند استدعاء طريقة الخدمة) عن مستخدمي الخدمة.

متى يجب استخدام؟أود أن أقول أنه عندما يكون التطبيق كبيرًا بما يكفي، فإن تغليف المنطق في وحدات منفصلة، ​​مع رسم بياني للتبعية بين الوحدات يعطي مكسبًا في سهولة القراءة وقابلية الاستكشاف للكود.

حقن التبعية هو نوع من تنفيذ "قلب السيطرة"المبدأ الذي يقوم عليه بناء الأطر.

إطار أعمال كما هو مذكور في "نمط التصميم" لـ GoF، هناك فئات تنفذ منطق تدفق التحكم الرئيسي مما يرفع المطور للقيام بذلك، وبهذه الطريقة تدرك الأطر قلب مبدأ التحكم.

طريقة التنفيذ كتقنية، وليس كتسلسل هرمي للفئة، مبدأ IoC هذا هو مجرد حقن التبعية.

دي يتكون بشكل أساسي من تفويض تعيين مثيلات الفئات وكتابة مرجع لتلك المثيلات إلى "كيان" خارجي:كائن، فئة ثابتة، مكون، إطار عمل، الخ ...

مثيلات الفئات هي "التبعيات"، الربط الخارجي لمكون الاستدعاء مع مثيل الفئة من خلال المرجع هو"حقنة".

من الواضح أنه يمكنك تنفيذ هذه التقنية بعدة طرق كما تريد من وجهة نظر OOP، انظر على سبيل المثال حقن المنشئ, حقن واضعة, حقن الواجهة.

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

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top