فهم دورة حياة حقن التبعية: Singleton و Scoped و Transient مع أمثلة مفصلة

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

عند تطبيق التزويد الإعتمادي، لدي المطورين ثلاثة خيارات تحدد مدى حياة الINSTANCES:

  1. الفردي الوحيد
  2. محدد النطاق
  3. مؤقت

بينما يعرف معظم المطورين هذه العبارات، يواجه عدد كبير منهم صعوبة في تحديد الخيار المناسب لمدى حياة الخدمة.

التعاريف

دعوني أبدأ بالتعاريف:

  • الفردي الوحيدتُنشئ INSTANCES الخدمة مرة واحدة لكل تطبيق من الحاوية الخدمية. سيخدم مثال واحد جميع الطلبات اللاحقة. تتم إزالة الخدمات الفردية الوحيدة في نهاية التطبيق (مثلا، عند إعادة تشغيل التطبيق).
  • مؤقتتُنشئ INSTANCES الخدمة لكل طلب من الحاوية الخدمية. تتم إزالة الخدمات المؤقتة في نهاية الطلب.
  • محدد النطاقتُنشئ INSTANCES الخدمة مرة واحدة لكل طلب العميل. تتم إزالة الخدمات المؤقتة في نهاية الطلب.

متى ينبغي استخدامها

  • موجود واحد – عندما تريد استخدام تنسيق فردي للخدمات طوال مراحل حياة التطبيق
  • قابل للتغيير – عندما تريد استخدام مثالي للخدمات في طلب العميل
  • محدد – عندما تريد استخدام تنسيق واحد للخدمة لكل طلب

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

لا تقلق ، دعونا نفهم مع مثال.

مثال

أولاً ، دعونا نخلق واجهات/خدمات وصفات:

C#

 

	// نحن نشهد 3 خدمات أساسية أدناه
		Public interface ISingleton
		Public interface ITransient 
		Public interface IScoped 

والآن دعونا نكتب تطبيقات كل خدمة واجهة/خدمة معلومات التوجيه أعلاه. سنحاول فهم المفهوم من خلال محاولة تحديث متغيرات callMeSingleton، callMeTransient، و callMeScoped.

  • تطبيق صنف الموجود الواحد:
C#

 

class SingletonImplementation: ISingleton
{
	var callMeSingleton = ""

	// تطبيق آخر
	public SetSingleton(string value)
	{
		callMeSingleton = value;
	}
	// تطبيق آخر
}

  • تطبيق القابل للتغيير:
C#

 

class TransientImplementation: ITransient 
{
	var callMeTransient = ""
	
	// تطبيق آخر
	public SetTransient(string value)
	{
		callMeTransient = value;
	}
	// تطبيق آخر
}

  • تطبيق المحدد:
C#

 

class ScopedImplementation: IScoped 
{
	var callMeScoped = ""
			
	// تطبيق آخر
	public SetScoped(string value)
	{
		callMeScoped = value;
	}
	// تطبيق آخر		
}

دعونا نسجل (ConfigureServices) مع التوزيع المتكامل (DI) لتقرير مراحل حياة كل نسخة من تلك الخدمات:

C#

 

services.AddSingleton<ISingleton, SingletonImplementation>();
services.AddTransient<ITransient , TransientImplementation>();
services.AddScoped<IScoped , ScopedImplementation>();

لنستخدم/اتصل بهذه الخدمات من 3 صناف مختلفين (ClassA, ClassB, و ClassC) لفهم دورة حياة كل خدمة:

  • ClassA:
C#

 

public class ClassA
{
	private ISingleton _singleton;
	// من المبدأ لتمثيل 3 خدمات مختلفة نقدم
	public ClassA(ISingleton singleton,
                  ITransient _transient,
                  IScoped _scoped)
	{
		_singleton = singleton;
	}
		
	public void UpdateSingletonFromClassA()
	{
		_singleton.SetSingleton("I am from ClassA");
	} 
	
  	public void UpdateTransientFromClassA()
    {
		_transient.SetTransient("I am from ClassA");
	} 
	
  	public void UpdateScopedFromClassA()
	{
		_scoped.SetScoped("I am from ClassA");
	}

	// تطبيقات أخرى 
}

  • ClassB:
C#

 

public class ClassB
{
	private ISingleton _singleton;
	// من المبدأ لتمثيل 3 خدمات مختلفة نقدم
	public ClassB(ISingleton singleton,
                  ITransient _transient,
                  IScoped _scoped)
	{
		_singleton = singleton;
	}
		
	public void UpdateSingletonFromClassB()
	{
		_singleton.SetSingleton("I am from ClassB");
	} 
	
  	public void UpdateTransientFromClassB()
    {
		_transient.SetTransient("I am from ClassB");
	} 
	
  	public void UpdateScopedFromClassB()
	{
		_scoped.SetScoped("I am from ClassB");
	}

	// تطبيقات أخرى 
}

  • ClassC:
C#

 

public class ClassC
{
	private ISingleton _singleton;
	// من المبدأ لتمثيل 3 خدمات مختلفة نقدم
	public ClassC(ISingleton singleton,
                  ITransient _transient,
                  IScoped _scoped)
	{
		_singleton = singleton;
	}
		
	public void UpdateSingletonFromClassC()
	{
		_singleton.SetSingleton("I am from ClassC");
	} 
	
  	public void UpdateTransientFromClassC()
    {
		_transient.SetTransient("I am from ClassC");
	} 
	
  	public void UpdateScopedFromClassC()
	{
		_scoped.SetScoped("I am from ClassC");
	}

	// تطبيقات أخرى 
}

تحليل

دعونا نحلل النتائج والسلوك لكل دورة حياة من الأعمال أعلاه من التطبيق:

Singleton

سوف تستخدم جميع الصناف (ClassA, ClassB, و ClassC) نفس النسخة الوحيدة لصنف SingletonImplementation طوال دورة حياة التطبيق. هذا يعني أن خصائص وحقول وعمليات الصنف SingletonImplementation ستتم مشاركتها مع النسخ التي يتم استخدامها في جميع الصناف المتصلين. أي تحديثات لخصائص أو حقول ستتجاوز التغييرات السابقة.

على سبيل المثال، في الشفرة العلوية أعلاه، ClassA، ClassB، و ClassC كلها تستخدم SingletonImplementation الخدمة كنسجل واحد وتستخدم SetSingleton لتحديث المتغير callMeSingleton. في هذه الحالة، سيكون هناك قيمة واحدة للمتغير callMeSingleton لجميع ال solicitudes تحاول Acceder a esta propiedad. أي صنف يوصله أخيراً لتحديث سيتبادل قيمة callMeSingleton الحالية.

  • ClassA – سيمتلك نفس تنسيقها مع الصناف الآخرين لخدمة TransientImplementation.
  • ClassB – سيمتلك نفس التنسيق التي يمتلكها الصناف الآخرين لخدمة TransientImplementation.
  • ClassC – سيمتلك نفس التنسيق الذي يمتلكه الصناف الآخرين لخدمة TransientImplementation.

ClassA، ClassB، و ClassC يحددون نفس النسخة لصنف SingletonImplementation، وسيتبادل قيمة callMeSingleton الحالية. لذا، تحقق من حذف أو تحديد الخصائص في تطوير نسجل الوحيدة.

تُحرِض الخدمات الوحيدة في نهاية التطبيق (أي عندما يعيد تشغيل التطبيق).

Transient

كل الأقسام (`ClassA`, `ClassB`, و `ClassC`) ستستخدم مناسباتها الفردية للصف `TransientImplementation`. هذا يعني أنه إذا كان واحد من الأقسام يطلب خصائص، حقول أو عمليات `TransientImplementation`, فإنه سيتم تحديث أو تغيير قيم مناسبته الفردية فقط. أي تحديثات لخصائص أو حقول لا تتشارك مع أي من النساء الأخرى من `TransientImplementation`.

دعونا نفهم:

  • ClassA – سيكون لها مناسبة فردية لخدمة `TransientImplementation`.
  • ClassB – سيكون لها مناسبة فردية لخدمة `TransientImplementation`.
  • ClassC – سيكون لها مناسبة فردية لخدمة `TransientImplementation`.

لنفترض أنك لديك `ClassD` التي تطلب الخدمة المفتوحة من تمام النساء الفردية من `ClassA`, `ClassB` و `ClassC`. في هذه الحالة، ستتم تعامل كل من النساء كنساء مختلفة/منفصلة وسيكون لكل من الأقسام قيمة `callMeTransient` خاصة به. انظر إلى التعليمات الداخلية أدناه ل `ClassD`:

C#

 

public ClassD
{
	// تنفيذ آخر
		
    // السطر التالي من الكود سيحدث قيمة callMeTransient إلى "أنا من ClassA" للمثيل الواحد من ClassA فقط.
    // ولن يتغير بسبب أي مكالمات لاحقة من Class B أو B class
	ClassA.UpdateTransientFromClassA(); 		
       
    // السطر التالي من الكود سيحدث قيمة callMeTransient إلى "أنا من ClassB" للمثيل الواحد من ClassB فقط.
    // ولن يتغير قيمة calssA ولا يتم تغييرها بالمكالمات اللاحقة من Class C
	ClassB.UpdateTransientFromClassB(); 
    
    // السطر التالي من الكود سيحدث قيمة callMeTransient إلى "أنا من ClassC" للمثيل الواحد من ClassC فقط.
    // ولن يتغير قيمة calssA وclassB ولا يتم تغييرها بأي مكالمات لاحقة من أي فئة أخرى.
    ClassC.UpdateTransientFromClassC(); 

	// تنفيذ آخر
}

تتم إزالة الخدمات العابرة في نهاية كل طلب. استخدم العابرة عندما تريد سلوك بدون حالة داخل الطلب.

Scoped

جميع الفئات (ClassA, ClassB, و ClassC) ستستخدم مثيل واحد من فئة ScopedImplementation لكل طلب. هذا يعني أن المكالمات للخصائص/الحقول/العمليات على فئة ScopedImplementation ستحدث على مثيل واحد في نطاق الطلب. أي تحديثات للخصائص/الحقول ستتم مشاركتها بين الفئات الأخرى.   

لنفهم:

  • ClassA – سيكون لديه مثيل الخدمة من TransientImplementation
  • – سيحصل على نفس مثيله الخدمة من TransientImplementation كـ .
  • – سيحصل على نفس مثيله الخدمة من TransientImplementation كـ و .

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

C#

 

public class ClassD
{
  // تنسيق آخر
 
  // سيتم تحديث قيمة callMeScoped إلى "أنا من الصف ال A" للملف التي لوظيفة الصف ال A
  // ولكن لأنها طول العمر المحدد للحيز لذا يمسك بنسخة واحدة من تنسيق التطبيقات المحددة
  // ثم يمكن أن يتم تحويله من قبل مجموعة تلقائية من الطلب من الصف ال B أو ال C
	ClassA.UpdateScopedFromClassA();  
  
  // سيتم تحديث قيمة callMeScoped إلى "أنا من الصف ال B" للملف الواحد من تنسيق التطبيقات المحددة 
  // وسيتم تحويل قيمة callMeScoped للملف التي لوظيفة الصف ال A أيضًا. 
	ClassB.UpdateScopedFromClassB();
  
  // الآن إذا قام الصف ال A بعملية على تنسيق التطبيقات المحددة،
  // سيستخدم الخصائص/قيم الحقل الأخيرة التي تم تحويلها من قبل الصف ال B.
	
  // سيتم تحديد قيمة callMeScoped إلى "أنا من الصف ال C"
  // وسيتم تحويل قيمة callMeScoped للملف التي لوظيفة الصف ال A وال B أيضًا.
	ClassC.UpdateScopedFromClassC(); 
  // إذا قام الصف ال B أو ال A بعملية على تنسيق التطبيقات المحددة، سيستخدم الخصائص/قيم الحقل الأخيرة التي تم تحويلها من قبل الصف ال C.

    // تنسيق آخر
}

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

وقت المعلومات الفريدة

قاعدة حياة الخدمة يمكن تغييرها من خلال الخدمة الوالدة التي يمكن تعيينها. غامض؟ دعوني أشرح:

دعونا نأخذ مثال مماثل من الصفوف العلمية أعلاه و نبدأ تكوين الخدمات المتجاوزة والمناسبة من SingletonImplementation (وهو خدمة واحدة) كما يلي. سيقوم هذا بتشغيل الخدمات ITransient و IScoped و يحدد الحياة الطويلة لهذه الخدمات كخدمة الأب بحياة الوحدة الواحدة. في هذه الحالة لن يوجد في تطبيقك أي خدمات تختلف أو متجاوزة (وافتراضًا إذا كان لديك هذه ال3 خدمات التي كنا نستخدمها في مثالينا).

اقرأ عبر السطور في التنسيق التالي:

C#

 

public class SingletonImplementation: ISingleton
{
	// المبني لإضافة تنشيط الخدمات.
	private readonly ITransient _transient 
	private readonly IScoped _scoped 
		
	SingletonImplementation(ITransient transient, IScoped scoped)
	{
		_transient = transient;  
        // الآن _transient سيتصرف كخدمة واحدة بغض النظر عن كيف تم تسجيله كخدمة تختلف
		_scoped = scoped;
        // الآن المتجاوز سيتصرف كخدمة واحدة بغض النظر عن أنه تم تسجيله كخدمة متجاوزة
	}
    var callMeSingleton = ""
		
	// تطبيقات أخرى
}

الخلاصة

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

Source:
https://dzone.com/articles/understanding-the-dependency-injection-lifecycle