design patterns - شرح - ما هو حقن التبعية؟




ما هو inversion of control (20)

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

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


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

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

كيف يعمل Dependency Injection في الربيع:

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

انقلاب التحكم (IOC)

IOC هو مفهوم عام ويمكن التعبير عنه بالعديد من الطرق المختلفة ويعتبر Dependency Injection أحد الأمثلة الملموسة على 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 المستندة إلى Set بواسطة أساليب تعيين أداة استدعاء الحاوية على الفاصوليا بعد استدعاء مُنشئ بدون حجة أو طريقة مصنع ثابتة بلا حجة لإنشاء مثيل لك.

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 يمكن استخدام تعليق على أداة تعيين لجعل المستقرات تبعيات مطلوبة.


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

على سبيل المثال ، ضع في اعتبارك هذه المجموعات:

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

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


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

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

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

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 مشابه لهذا في عالم البرمجة Object Oriented. القيم الموجودة هناك بدلاً من القيم الحرفية الثابتة هي كائنات كاملة - ولكن السبب في نقل الشفرة التي تنشئها خارج رمز الصف مشابه - تتغير الكائنات بشكل متكرر أكثر ثم الرمز الذي يستخدمها. إحدى الحالات المهمة التي يحتاج فيها هذا التغيير هي الاختبارات.


دعنا نتخيل أنك تريد الذهاب إلى الصيد:

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

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


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

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 = new Car();

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

بعبارة أخرى مع هذا النهج هو أن لدينا فئة السيارات عالية المستوى تعتمد على المستوى الأدنى من فئة GasEngine التي تنتهك مبدأ انقلاب التبعية (DIP) من SOLID. ويرى مجمع دبي للاستثمار أنه ينبغي لنا أن نعتمد على الأفكار التجريدية وليس على فئات محددة. لذلك من أجل تلبية هذا نقدم واجهة 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. هنا يأتي دور Dependency Injection

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

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

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

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

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

استكمال: شاهدت دورة حول EF Core من جولي ليرمان في الآونة الأخيرة ، وأحب أيضا تعريفها القصير حول DI.

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


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

(وتبسيط العمليات، نعم لقد أصبحت مفرطة في فقت اسم 25 $ لبسيطة إلى حد ما، مفهوم) ، يا .25سنتا


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

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

على سبيل المثال ، ضع في اعتبارك كائن Car .

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

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

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

  //The rest
}

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

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

ثم ماذا Dependency Injection لنا ...؟

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

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

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

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

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


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

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

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

الصورة أعلاه هي صورة مسجل الشريط المحمول Reel-to-reel ، في منتصف القرن العشرين. Source .

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

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

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

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

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

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

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

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

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

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

سابقا نحن نكتب رمز مثل هذا

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)؟

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

DI، DIP و SOLID

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

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

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

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

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

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

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

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

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

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

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

متى تستخدم DI؟

  • حيث من المحتمل أن تكون هناك حاجة لاستبدال تبعية لتنفيذ مماثل ،
  • في أي وقت تحتاج فيه إلى اختبار وحدة طرق الفصل بمعزل عن تبعياته ،
  • إذا كان عدم اليقين بشأن عمر التبعية قد يبرر التجريب (على سبيل المثال مهلا ، MyDepClass يكون خيطًا آمنًا - ماذا لو 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 باعتباره تبعية ، MyLogger فقط مع واجهة بسيطة:

public interface IClock
{
    DateTime Now { get; }
}

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

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 / Serialization DTOs / Entity Graphs / Anonymous JSON projections et al - أي "بيانات فقط" أو السجلات - المستخدمة أو التي يتم إرجاعها من الطرق باعتبارها تبعيات (في الشعور UML) وليس خاضعة ل DI. استخدام لإظهار هذه على ما يرام.new


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

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

حقن التبسيط هو مجرد لصق فئتين وفي نفس الوقت الاحتفاظ بها منفصلة.


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


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

لا تبعية:

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

الاعتماد:

public class Client {
    Service service;

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

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

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


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

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

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

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

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


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

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

نحن ندعو فقط أن تمرير المعلمات في منشئ. كنا نفعل ذلك بانتظام منذ اخترع الصانعين.

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

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

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

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

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

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


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

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

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


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

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

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


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

public class MyDao {

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

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

}

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

الطريق 1)

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)
  {...}

}

الطريق 2)

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

الطريق 3)

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

1) 2) 3) عن طريق

gcc foo.o bar.o -o bar

مزايا

  • بسيط

سلبيات

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

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

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

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

عن طريق

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

عن طريق

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

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

تحقق من https://en.wikipedia.org/wiki/Dependency_injection

===

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

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

عن طريق

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

2) استخدام المكتبة: للحصول على Android dagger2

مزايا

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

سلبيات

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

تعريف الاعتمادية حقن

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

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



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

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







terminology