أشارك إحدى مكتباتي تسمى First DI. لقد ساعدني لسنوات عديدة على حل مشكلة حقن التبعية في تطبيقات المتصفح للمكتبات مثل React و Preact و Mithril وغيرها. عند كتابة First DI ، تم أخذ أيديولوجية مكتبات DI من لغات C # و Java ، مثل autofac و java spring autowired و ninject وغيرها ، كأساس. تمامًا مثل المكتبات من هذه اللغات ، يعمل First DI استنادًا إلى واجهات الانعكاس والنسخ المطبعي.ما هو DI؟
باختصار ، يعد Dependecy Injection (المشار إليه فيما يلي باسم DI) أحد الأجزاء الرئيسية لبناء بنية نظيفة. مما يسمح لك باستبدال أي جزء من البرنامج بتنفيذ آخر لهذا الجزء من البرنامج. و DI هي أداة لتنفيذ واستبدال أجزاء من البرنامج.على وجه الخصوص ، يسمح لك:- استبدل تطبيقات المنطق بشكل فردي لكل منصة. على سبيل المثال ، في المتصفح ، اعرض الإشعارات من خلال واجهة برمجة تطبيقات الإشعارات ، وعند تجميع تطبيقات الجوال ، استبدلها بإشعارات من خلال واجهة برمجة تطبيقات النظام الأساسي للجوال.
- استبدال تنفيذ المنطق في بيئات مختلفة. على سبيل المثال ، يمكن استخدام بوابة الدفع أو نظام توزيع الإشعارات الذي يتعذر الوصول إليه من الجهاز المحلي للمطور في دائرة المنتج. وبالتالي ، بدلاً من المدفوعات الحقيقية أو البريد في مرحلة التطوير ، يمكن استبدال المنطق بكعب.
- استبدل مصدر البيانات. على سبيل المثال ، بدلاً من طلب حقيقي إلى الخادم ، قم بحذف البيانات المعدة مسبقًا لاختبار منطق الأعمال أو التخطيط للامتثال للتخطيطات.
- والعديد من الاستخدامات المفيدة ، ولكن ليس عن ذلك الآن ...
للبدء ، قم بإعداد الأدوات للعمل.التحضير للاستخدام
للاستخدام ، تحتاج فقط إلى 3 أشياء بسيطة:- قم بتوصيل ملف بيانات التعريف العكسي .
- في ملف tsconfig ، قم بتمكين emitDecoratorMetadata وخيارات التجريبية التجريبية. الأول يسمح لك بتوليد التفكير ، والثاني يشمل دعم الديكور
- قم بإنشاء أي ديكور فارغ ، على سبيل المثال const reflection = (..._ params: Object []): void => {} أو استخدم الديكور الجاهز من المكتبة.
الآن سأوضح الاستخدام في أبسط أشكاله.استخدام بسيط
على سبيل المثال ، خذ برنامجًا مكتوبًا باستخدام Pure Architecture:import { autowired, override, reflection } from "first-di";
@reflection
class ProdRepository {
public async getData(): Promise<string> {
return await Promise.resolve("production");
}
}
@reflection
class MockRepository {
public async getData(): Promise<string> {
return await Promise.resolve("mock");
}
}
@reflection
class ProdService {
constructor(private readonly prodRepository: ProdRepository) { }
public async getData(): Promise<string> {
return await this.prodRepository.getData();
}
}
class ProdController {
@autowired()
private readonly prodService!: ProdService;
public async getData(): Promise<string> {
return await this.prodService.getData();
}
}
if (process.env.NODE_ENV === "test") {
override(ProdRepository, MockRepository);
}
const controllerInstance = new ProdController();
const data = await controllerInstance.getData();
if (process.env.NODE_ENV === "test") {
assert.strictEqual(data, "mock");
} else {
assert.strictEqual(data, "production");
}
في Pure Architecture ، تحتاج إلى إضافة سطرين فقط من التعليمات البرمجية ، يتم تشغيلهما تلقائيًا وتجاوزهما حتى تبدأ شركة DI العمل. والآن البرنامج لديه بالفعل تنفيذ للإنتاج وتنفيذ للاختبار.منذ كتابة First DI في المقام الأول لتطبيقات العميل ، بشكل افتراضي سيتم تنفيذ التبعيات كـ Singleton. من أجل تغيير هذا السلوك بشكل عام ، هناك خيارات افتراضية ، بالإضافة إلى معلمات لمزين الديكور التلقائي وطريقة التجاوز ().يمكنك تغيير ما يلي:
import { DI, AutowiredLifetimes } from "first-di";
DI.defaultOptions.lifeTime = AutowiredLifetimes.PER_INSTANCE;
import { autowired, AutowiredLifetimes } from "first-di";
@autowired({lifeTime: AutowiredLifetimes.PER_INSTANCE})
private readonly prodService!: ProdService;
import { override, AutowiredLifetimes } from "first-di";
override(ProdRepository, MockRepository, {lifeTime: AutowiredLifetimes.PER_INSTANCE});
هذه هي أسهل طريقة لاستخدام DI. ولكن في المؤسسة ، بدلاً من تطبيقات محددة ، يتم استخدام الواجهات ، وهي عقد للتطبيقات التي سيتم تنفيذها. هناك طريقة ثانية للتشغيل لهذا.الاستخدام المهني
للاستخدام المهني ، نستبدل تطبيقات محددة بواجهات. ولكن لا تولد الآلة المطبعية رمزًا لتشغيل الواجهات في وقت التشغيل. لحسن الحظ ، هناك حل بسيط ، لفهم أنه من الضروري تذكر النظرية ... ما هي الواجهة؟ هذه فئة مجردة تماما!لحسن الحظ ، يدعم Typcript فئات مجردة بالكامل ، وينشئ رمز وقت التشغيل ، ويسمح باستخدام فئات مجردة لوصف الأنواع.سنستخدم هذه المعلومات ونكتب برنامج مؤسسة:import { autowired, override, reflection } from "first-di";
abstract class AbstractRepository {
abstract getData(): Promise<string>;
}
@reflection
class ProdRepository implements AbstractRepository {
public async getData(): Promise<string> {
return await Promise.resolve("production");
}
}
@reflection
class MockRepository implements AbstractRepository {
public async getData(): Promise<string> {
return await Promise.resolve("mock");
}
}
abstract class AbstractService {
abstract getData(): Promise<string>;
}
@reflection
class ProdService implements AbstractService {
private readonly prodRepository: AbstractRepository;
constructor(prodRepository: AbstractRepository) {
this.prodRepository = prodRepository;
}
public async getData(): Promise<string> {
return await this.prodRepository.getData();
}
}
class ProdController {
@autowired()
private readonly prodService!: AbstractService;
public async getData(): Promise<string> {
return await this.prodService.getData();
}
}
override(AbstractService, ProdService);
if (process.env.NODE_ENV === "test") {
override(AbstractRepository, MockRepository);
} else {
override(AbstractRepository, ProdRepository);
}
const controllerInstance = new ProdController();
const data = await controllerInstance.getData();
if (process.env.NODE_ENV === "test") {
assert.strictEqual(data, "mock");
} else {
assert.strictEqual(data, "production");
}
الآن لدينا برنامج احترافي جاهز ليس له تنفيذ افتراضي ، ويتم استبدال جميع العقود بتنفيذها بالفعل في مرحلة تجميع التطبيقات.من السهل جدًا تضمين DI في تطبيقك. وسيتمكن أولئك الذين كتبوا في C # و Java من استخدام التجربة الحالية في تطوير الويب.ميزات أخرى
باستخدام نسخ متعددة من DI:import { DI } from "first-di";
import { ProductionService } from "../services/ProductionService";
const scopeA = new DI();
const scopeB = new DI();
export class Controller {
@scopeA.autowired()
private readonly serviceScopeA!: ProductionService;
@scopeB.autowired()
private readonly serviceScopeB!: ProductionService;
public async getDataScopeA(): Promise<string> {
return await this.serviceScopeA.getData();
}
public async getDataScopeB(): Promise<string> {
return await this.serviceScopeB.getData();
}
}
القابلية للتوسعة ، يمكنك كتابة DI الخاص بك:import { DI } from "first-di";
class MyDI extends DI {
public getAllSingletons(): IterableIterator<object> {
return this.singletonsList.values();
}
}
والعديد من الميزات الأكثر إثارة للاهتمام موصوفة في المستودع على GitHub .لماذا كتابتك الخاصة ولا تستخدم؟
والسبب بسيط - عندما تم كتابة هذا DI لم تكن هناك بدائل. كان هذا الوقت عندما لم تعد الزاوية 1 ذات صلة ، ولم تكن الزاوية 2 للخروج. ظهرت الفصول الدراسية للتو في Javascript ، والتأمل في الكتابة المطبوعة. بالمناسبة ، أصبح ظهور التفكير الدافع الرئيسي للعمل.InversifyJS - حتى في شكله الحالي لا يناسبني. الكثير من المراجل. بالإضافة إلى ذلك ، فإن التسجيل عن طريق الخط أو الشخصية على الجانب الآخر يقتل إمكانية إعادة تبعية إعادة التبعية.احب؟
إذا كنت تحب هذا DI يساعد على جعله أكثر شعبية. جربها ، أرسل الطلبات ، ضع النجوم. مستودع جيثب .