كودين. الأساسيات

لم أجد أدلة مفهومة لأولئك الذين Kodein لأول مرة ، والوثائق ليست شفافة ومتسقة في جميع الأماكن ، لذلك أريد أن أشارككم السمات الرئيسية للمكتبة. سيتم إصدار بعض ميزات المكتبة ، ولكن هذا هو الجزء المتقدم بشكل أساسي. ستجد هنا كل شيء للبدء بشكل طبيعي والبدء في تطبيق التبعيات مع Kodein أثناء قراءة المقال. تستند المقالة إلى Kodein 5.3.0 ، نظرًا لأن Kodein 6.0.0 يتطلب Support Library 28 أو AndroidX وسيكون بعيدًا عن أن يتحول الجميع إليها لأن العديد من مكتبات الأطراف الثالثة لم تقدم بعد إصدارات متوافقة.


Kodein هي مكتبة لتنفيذ حقن التبعية (DI). إذا لم تكن على دراية بهذا المفهوم ، فاقرأ بداية المقال حول Dagger2 ، حيث يشرح المؤلف بإيجاز الجوانب النظرية لـ DI.

في هذه المقالة ، سننظر في كل شيء باستخدام مثال Android ، ولكن وفقًا للمطورين ، يتصرف Kodein كما هو في جميع الأنظمة الأساسية التي تدعمها Kotlin (JVM ، Android ، JS ، Native).

التثبيت


نظرًا لحقيقة أن Java تحتوي على type erasure ، تنشأ مشكلة - يقوم المحول البرمجي بمسح النوع العام. على مستوى الرمز البريدي ، List<String> وقائمة List<Date> ليست سوى List . ومع ذلك ، لا تزال هناك طريقة للحصول على معلومات حول الأنواع العامة ، ولكنها ستكلف الكثير وتعمل فقط على JVM و Android. في هذا الصدد ، يقترح مطورو Kodein استخدام واحدة من اثنين من التبعيات: واحد يتلقى معلومات حول الأنواع المعممة ( kodein-generic ) أثناء العمل ، والآخر لا ( kodein-erased ). على سبيل المثال ، عند استخدام List<String> kodein-erased List<String> List<Date > سيتم حفظها على شكل List<*> ، وعند استخدام kodein-generic سيتم حفظ كل شيء جنبًا إلى جنب مع النوع المحدد ، أي ، List<String> List<Date> على التوالي.

كيف تختار؟

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

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

لذلك ، تثبيت:

أولا - في build.gradle بين المستودعات يجب أن يكون jcenter () ، إذا لم يكن هناك - إضافة.

 buildscript { repositories { jcenter() } } 

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

 implementation "org.kodein.di:kodein-di-generic-jvm:$version" 

 implementation "org.kodein.di:kodein-di-erased-jvm:$version" 

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

هناك أيضا خيارات هنا.

أولاً ، أنت لا تستخدم SupportLibrary

 implementation "org.kodein.di:kodein-di-framework-android-core:$version" 

الثاني - الاستخدام

 implementation "org.kodein.di:kodein-di-framework-android-support:$version" 

ثالثا - أنت تستخدم AndroidX

 implementation "org.kodein.di:kodein-di-framework-android-x:$version" 

نبدأ في خلق التبعيات


باستخدام Dagger2 ، اعتدت على إنشاء وتهيئة التبعيات عند بدء تشغيل التطبيق ، في فئة التطبيق.

مع Kodein ، يتم ذلك مثل هذا:

 class MyApp : Application() { val kodein = Kodein { /*  */ } } 

إعلانات التبعية تبدأ دائمًا بـ

 bind<TYPE>() with 

العلامات


تعد علامات تبعية Kodein ميزة مشابهة في وظيفة " Qualifier من Dagger2 . في Dagger2 يلزمك إما إجراء Qualifier منفصلة أو استخدام @Named("someTag") ، وهو في الواقع أيضًا Qualifier . خلاصة القول بسيطة - وبهذه الطريقة يمكنك التمييز بين اثنين من التبعية من نفس النوع. على سبيل المثال ، تحتاج إلى الحصول على ontext تطبيق أو Activity معين حسب الموقف ، لذلك تحتاج إلى تحديد علامات لهذا عند الإعلان عن التبعيات. يسمح Kodein بالإعلان عن تبعية واحدة دون علامة ، وستكون القاعدة واحدة وإذا لم تحدد العلامة عند تلقي التبعية ، فسوف نحصل عليها ، وسيلزم وضع علامة على الآخرين ، وعندما يتم تلقي التبعية ، يجب تحديد العلامة.

 val kodein = Kodein { bind<Context>() with ... bind<Context>(tag = "main_activity") with ... bind<Context>(tag = "sale_activity") with ... } 

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

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


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

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


لنبدأ بمثال:

 val kodein = Kodein { bind<IMyDatabase>() with singleton { RoomDb() } } 

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

 val kodein = Kodein { bind<IMyDatabase>() with singleton(sync = false) { RoomDb() } } 

إذا كنت بحاجة إلى إنشاء مثيل تبعية ليس في المكالمة الأولى ، ولكن بعد إنشاء مثيل Kodein ، استخدم وظيفة أخرى:

 val kodein = Kodein { bind<IMyDatabase>() with eagerSingleton { RoomDb() } } 

خلق باستمرار مثيل جديد من التبعية


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

 val kodein = Kodein { bind<IMainPresenter>() with provider { QuantityPresenter() } } 

في هذه الحالة ، في كل مرة نطلب فيها تبعية IMainPresenter ، سيتم إنشاء مثيل جديد لـ QuantityPresenter .

قم بإنشاء مثيل جديد من التبعية باستمرار وقم بتمرير المعلمات إلى المُنشئ


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

 val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } } 

في كل مرة نقوم بإنشاء مثيل مخزنة مؤقتًا اعتمادًا على المعلمات


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

 val kodein = Kodein { bind<IRandomIntGenerator>() with multiton { from: Int, to: Int -> IntRandom(from, to) } } 

في المثال أعلاه ، عندما نحصل على التبعية لأول مرة مع المعلمتين 5 و 10 IntRandom(5, 10) نسخة جديدة من IntRandom(5, 10) ، عندما نسمي التبعية مرة أخرى بنفس المعلمات ، سنحصل على المثيل الذي تم إنشاؤه مسبقًا. وبالتالي ، يتم الحصول على map من المفرد مع التهيئة البطيئة. الحجج ، كما في حالة factory الحد الأقصى 5 .

كما هو الحال مع المفردات ، يمكنك تعطيل التزامن هنا.

 val kodein = Kodein { bind<IRandomIntGenerator>() with multiton(sync = false) { from: Int, to: Int -> IntRandom(from, to) } } 

استخدام الروابط الضعيفة والضعيفة في كودين


عند توفير التبعيات باستخدام singleton أو multiton يمكنك تحديد نوع المرجع للمثيل المخزن. في الحالة المعتادة ، التي نظرنا فيها أعلاه - سيكون هذا الرابط strong المعتاد. لكن من الممكن استخدام روابط weak . إذا كنت جديدًا على هذه المفاهيم ، فاحصل هنا .

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

 val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } } 

مفردة منفصلة لكل تيار


هذا هو نفس المفرد ، ولكن لكل مؤشر ترابط يطلب تبعية ، سيتم إنشاء مفردة. للقيام بذلك ، استخدم المعلمة المألوفة ref .

 val kodein = Kodein { bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) } } 

الثوابت كما التبعيات القابلة للتضمين


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

 val kodein = Kodein { constant(tag = "maxThread") with 8 constant(tag = "serverURL") with "https://my.server.url" 

إنشاء التبعيات دون تغيير النوع


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

 val kodein = Kodein { bind() from singleton { Gson() } 

سيكون التبعية في المثال أعلاه نوع الإرجاع للدالة ، أي أنه سيتم Gson تبعية لنوع Gson .

إنشاء تبعيات فئة فرعية من الطبقة العليا أو واجهة


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

 val kodein = Kodein { bind<Animal>().subTypes() with { animalType -> when (animalType.jvmType) { Dog::class.java -> eagerSingleton { Dog() } else -> provider { WildAnimal(animalType) } } } 

يمكن أن تكون فئة Animal إما فئة .subtypes أو واجهة ، باستخدام .subtypes نحصل على نوع animalType أنواع animalType النوع TypeToken<*> ، والذي يمكننا من خلاله بالفعل الحصول على فئة Java ، TypeToken<*> لذلك ، نقدم تبعية بطرق مختلفة. يمكن أن تكون هذه الميزة مفيدة إذا كنت تستخدم TypeToken أو مشتقاته كمعلمة مُنشئ لعدد من الحالات. وبهذه الطريقة أيضًا ، يمكنك تجنب التعليمات البرمجية غير الضرورية بنفس إنشاء التبعية لأنواع مختلفة.

قم بإنشاء تبعيات تحتاج إلى تبعيات أخرى كمعلمات


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

 class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway 

لإنشاء فئة ذات تبعيات تم إنشاؤها مسبقًا في Kodein يكفي تمرير استدعاء دالة () كمعلمات. في هذه الحالة ، ترتيب الخلق غير مهم.

 bind<IDispatchersContainer>() with singleton { DispatchersContainer() } bind<IProductGateway>() with singleton { ProductGateway(instance(), instance()) } bind<IProductApi>() with singleton { ProductApi() } 

بدلاً من instance() قد تكون هناك مكالمات إلى provider() أو factory() ؛ سنلقي نظرة فاحصة على هذه الطرق في القسم الخاص بالحصول على التبعيات وتنفيذها.

قم بإنشاء تبعية عن طريق استدعاء أسلوب التبعية الذي تم إنشاؤه مسبقًا


لا يبدو هذا جيدًا جدًا ، ولكن يمكنك استدعاء instance<TYPE> للحصول على فصل دراسي نوفره بالفعل في مكان ما والاتصال بأسلوب هذه الفئة للحصول على تبعية جديدة.

 bind<DataSource>() with singleton { MySQLDataSource() } bind<Connection>() with provider { instance<DataSource>().openConnection() } 

وحدات


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

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } val kodein: Kodein = Kodein { import(appModule) bind<ISchedulersContainer>() with singleton { SchedulersContainer() } //   } 

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

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

 import(apiModule.copy(name = "firstAPI")) import(secondApiModule.copy(prefix = "secondAPI-")) 

اعتدت على العمل عندما تكون الوحدات معتمدة على بعضها البعض وتشكل نوعا من التسلسل الهرمي. Kodein استيراد كل وحدة نمطية إلى Kodein مرة واحدة ، لذلك ، إذا حاولت استيراد وحدتين لهما نفس الوحدات التابعة في Kodein ، فسوف يتعطل التطبيق. الحل بسيط - تحتاج إلى استخدام importOnce(someModule) للاستيراد ، والذي سيتحقق مما إذا كانت الوحدة النمطية التي تحمل الاسم نفسه قد تم استيرادها مسبقًا ، ثم الاستيراد إذا لزم الأمر.

على سبيل المثال ، في مثل هذه الحالات ، سيتعطل التطبيق:

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) } 

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { importOnce(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) } 

ولكن إذا كانت استدعاء importOnce في محاولة اتصال ثانية ، importOnce كل شيء. كن حذرا.

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { importOnce(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) } 

الميراث


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

 val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() } //   } val subKodein = Kodein { extend(kodein) //   } 

إعادة تعريف


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

 val kodein = Kodein { bind<Api>() with singleton { ApiImpl() } /* ... */ bind<Api>(overrides = true) with singleton { OtherApiImpl() } } 

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

لا يبدو هذا واضحًا جدًا ، فلنستخدم الأمثلة. في هذه الحالات ، سيتعطل التطبيق:

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule) } 

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) } 

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

  val appModule = Kodein.Module("app") { bind<Gson>(overrides = true) with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) } 

ولكن إذا كنت تريد ذلك بالفعل وفهمت ما تفعله ، فيمكنك إنشاء وحدة نمطية ، إذا كانت هناك تبعيات متطابقة مع كائن Kodein ستعيد تعريفها ولن يتعطل التطبيق. نستخدم المعلمة allowSilentOverride للوحدة النمطية.

 val testModule = Kodein.Module(name = "test", allowSilentOverride = true) { bind<EmailClient>() with singleton { MockEmailClient() } } 

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

استرجاع وحقن التبعيات


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

Kodein مطورو Kodein طريقتين للحصول على التبعيات - injection وإعادة retieval . باختصار ، يتم injection عندما يتلقى الفصل جميع التبعيات عند إنشائه ، أي في المنشئ ، ويكون retrieval عندما يكون الفصل نفسه مسؤولاً عن الحصول على التبعيات.

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

طرق Kodein لاستخدام التبعيات


يحتوي مثيل فئة Kodein على ثلاث طرق تُرجع تبعية أو مصنع تبعية أو موفر تبعية - instance() و factory() و provider() على التوالي. وبالتالي ، إذا قدمت تبعية باستخدام factory أو provider ، فيمكنك أن تتلقى ليس فقط نتيجة تنفيذ الوظيفة ، ولكن أيضًا الوظيفة نفسها. تذكر أنه يمكنك استخدام العلامات في جميع الأشكال.

  val kodein: Kodein = Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by factory() private val random: Random by instance() private val randomProvider: () -> Random by provider() 

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


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

 class ProductApi(private val client: HttpClient, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instance(), instance()) } } 

حقن التبعية في خصائص لاغية


قد لا تعرف جيدًا ما إذا كان قد تم الإعلان عن التبعية أم لا. إذا لم يتم الإعلان عن Kodein مثيل Kodein ، فسوف ينتج عن التعليمة البرمجية من المثال أعلاه Kodein.NotFoundException . إذا كنت ترغب في الحصول على قيمة null نتيجة لذلك ، إذا لم يكن هناك تبعية ، فهناك ثلاث وظائف مساعدة لهذا: instanceOrNull() factoryOrNull() و factoryOrNull() و providerOrNull() .

 class ProductApi(private val client: HttpClient?, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instanceOrNull(), instance()) } } 

الحصول على التبعيات داخل الفصل.


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

Activity ، Fragment والفئات الأخرى مع دورة حياتهم الخاصة ، كل شيء عنهم.

لتنفيذ التبعيات في Activity نحتاج فقط إلى رابط لمثيل Kodein ، وبعد ذلك يمكننا استخدام طرق معروفة. في الواقع ، لقد رأيت بالفعل أمثلة على retrieval أعلاه ، ما عليك سوى الإعلان عن عقار وتفويضه إلى إحدى الوظائف: instance() ، factory() أو provider()

 private val number: BigDecimal by kodein.instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by kodein.factory() private val random: Random? by kodein.instanceOrNull() private val randomProvider: (() -> Random)? by kodein.providerOrNull() 

تمرير المعلمات إلى المصانع


لقد رأيت بالفعل أنه من أجل تمرير معلمة إلى المصنع ، يكفي استخدام المعلمة arg الخاصة بوظيفة instance .ولكن ماذا لو كان هناك العديد من المعلمات (قلت في وقت سابق أنه يمكن أن يكون هناك ما يصل إلى 5 معلمات في المصنع تحتاج فقط إلى تمرير argفصل دراسي إلى المعلمة Mالتي تم تحميلها بشكل زائد على المنشئات ويمكن أن تأخذ من 2 إلى 5 وسائط.

 val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } } val picker: IColorPicker by kodein.instance(arg = M(255, 211, 175, 215)) 

قوة التهيئة التبعية


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

 val myTrigger = KodeinTrigger() val gson: Gson by kodein.on(trigger = myTrigger).instance() /*...*/ myTrigger.trigger() //     Gson 

 val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance() /*...*/ myTrigger.trigger() //        kodeinWithTrigger 

إنشاء مثيل كودين كسول


قبل ذلك ، أنشأنا باستمرار مثيلًا صريحًا Kodein، لكن من الممكن تأجيل تهيئة هذه الخاصية باستخدام فئة LazyKodeinتأخذ وظيفة في المُنشئ والتي يجب أن تُرجع كائنًا Kodein.

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

 val kodein: Kodein = LazyKodein { Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } } private val number: BigDecimal by kodein.instance(arg = "13.4") /* ... */ number.toPlainString() //     kodein    

استدعاء Kodein.lazy سيؤدي إلى نتيجة مماثلة.

  val kodein: Kodein = Kodein.lazy { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by kodein.instance(arg = "13.4") /* ... */ number.toPlainString() //     kodein    

Kodein تأخر التهيئة


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

 val kodein = LateInitKodein() val gson: Gson by kodein.instance() /*...*/ kodein.baseKodein = /*     Kodein */ /*...*/ gson.fromJson(someStr) 

الحصول على جميع مثيلات النوع المحدد


يمكنك أن تسأل Kodein عن مثيل من النوع المحدد وجميع أحفاده في النموذج List. كل شيء فقط داخل العلامة المحددة. للقيام بذلك، هناك طرق allInstances، allProviders، allFactories.

  val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by kodein.allInstances() 

إذا قمت بالطباعة إلى السجل ، فسترى هناك [32767 ، 136.88 ، 4562 ، 12.46]. التبعية مع العلامة ليست في القائمة.

تبسيط اكتساب التبعية باستخدام واجهة KodeinAware


تلزمك هذه الواجهة بتجاوز خاصية الكتابة Kodein، وفي المقابل توفر الوصول إلى جميع الوظائف المتاحة للمثيل Kodein.

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by allInstances() } 

كما ترون ، الآن يمكنك ببساطة الكتابة by allInstances()بدلاً من by kodein.allInstances()

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

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } override val kodeinTrigger = KodeinTrigger() val numbers: List<Number> by allInstances() override fun onCreate() { super.onCreate() kodeinTrigger.trigger() } } 

نظرًا لأن الوصول إلى التبعيات Kodeinوالمثيل كسول ، يمكنك تفويض تهيئة المثيل إلى Kodeinالوظيفة المدمجة في Kotlin lazy. قد يكون هذا النهج مفيدًا في الفئات وفقًا لسياقها ، على سبيل المثال ، في Activity.

 class CategoriesActivity : Activity(), KodeinAware { override val kodein: Kodein by lazy { (application as MyApplication).kodein } private val myFloat: Float by instance() 

لنفس الأسباب ، يمكنك استخدام معدل lateinit.

 class CategoriesActivity : Activity(), KodeinAware { override lateinit var kodein: Kodein private val myFloat: Float by instance() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) kodein = (application as MyApplication).kodein } 

الوصول إلى التبعيات دون تفويض الخصائص


إذا كنت لا تريد استخدام تفويض الممتلكات لسبب ما ، يمكنك استخدام الوصول المباشر من خلال DKodein(من مباشرة). الفرق الرئيسي هو أنه لن يكون هناك تهيئة كسولة بعد الآن ، وسيتم الحصول على التبعية فورًا في وقت الاتصال instance، providerووظائف مماثلة. يمكنك الحصول DKodeinعليه من مثيل Kodein الحالي أو الإنشاء من البداية.

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with singleton { BigDecimal.TEN } } val directKodein: DKodein = kodein.direct val directKodein2: DKodein = Kodein.direct { bind<BigDecimal>() with singleton { BigDecimal.ONE } } val someNumber:BigDecimal = directKodein.instance() val someNumber2:BigDecimal = directKodein2.instance() 

Kodein يمكن استخدامها في الإطار KodeinAware، وفي DKodeinالإطار DKodeinAware، يمكنك تجربة.

الحصول على التبعيات في أي سياق


من أجل الحصول على Kodeinالعديد من التبعيات من نفس النوع من كائن واحد ، قمنا بالفعل بفحص خيار استخدام العلامات والمصانع ذات الوسيطات ، ولكن هناك شيء آخر - لاستخدام السياق (وهذا ليس السياق الموجود في Android).

الاختلافات من التبعية مع العلامة:

  • لا يمكن استخدام العلامة داخل دالة ننشئ فيها تبعية
  • عند استخدام السياق ، يمكننا الوصول إلى مثيل السياق في وظيفة إنشاء التبعية

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

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

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with contexted<CategoriesActivity>().provider { context.getActivityBigDecimal() } bind<BigDecimal>() with contexted<CategoriesPresenter>().factory { initialValue:BigDecimal -> context.getPresenterBigDecimal(initialValue) } } } class CategoriesActivity : Activity(), AppKodeinAware { fun getActivityBigDecimal() = BigDecimal("16.34") private val activityBigDecimal: BigDecimal by kodein.on(context = this).instance() } class CategoriesPresenter : AppKodeinAware { fun getPresenterBigDecimal(initialValue: BigDecimal) = initialValue * BigDecimal.TEN private val presenterBigDecimal: BigDecimal by kodein.on(context = this).instance(arg = BigDecimal("31.74")) } 

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

لإعلان التبعية ، لا تحددها with provider()، ولكن with contexted<OurContextClass>().provider، أين OurContextClassهو نوع الفئة ، التي ستعمل مثيل لها كسياق. contextedيمكن أن يكون مزود فقط أو المصنع.

يتم الوصول إلى هذا السياق في الوظيفة التي تقوم بإرجاع التبعية من خلال متغير مسمى context.

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

وبالمثل ، يتم استخدام السياق في حالة injection.

 private val productApi: IProductApi by kodein.on(context = someContext).newInstance { ProductApi(instance(), instance()) } } 

ملحقات أندرويد


في بداية المقال ، وعدت بالنظر في خيارات التوسع ل Android.
لا شيء يمنعك من استخدامه Kodeinكما ناقشنا أعلاه ، ولكن يمكنك جعل كل شيء من حيث الحجم أكثر ملاءمة.

المدمج في Kodein لالروبوت


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

 class MyApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidModule(this@MyApplication)) //  } val inflater: LayoutInflater by instance() } 

كما ترون - يمكن أن تحصل، على سبيل المثال LayoutInflater. للحصول على قائمة كاملة بالتبعيات المعلنة في الوحدة - انظر هنا .

إذا كنت ترغب في الحصول على هذه التبعيات خارج فئات Android التي تدرك سياقها ، فحدد السياق بشكل صريح.

 val inflater: LayoutInflater by kodein.on(context = getActivity()).instance() 

الحصول بسرعة Kodein الأصل من خلال closestKodein ()


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

 class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() } 

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

 class MyController(androidContext: Context) : KodeinAware { override val kodein by androidContext.closestKodein() override val kodeinContext = kcontext(androidContext) val inflater: LayoutInflater by instance() } 

إنشاء Kodein منفصلة في النشاط


قد يكون من الضروري أن ترث من Kodein الأصل في النشاط وتوسيعه. الحل بسيط جدا.

 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) /*   */ } } 

Kodein الذي يخضع لتغيير التكوين


نعم تستطيع. هناك وظيفة لهذا الغرض retainedKodein. عند استخدامه ، Kodeinلن يتم إعادة إنشاء الكائن بعد تغيير التكوين.

 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by retainedKodein { extend(parentKodein) } } 

ما لا يقال في المقال؟


لم أكن أدعي أنها كاملة ، وأنا شخصياً لا أفهم بعض الأشياء جيدًا بما يكفي لمحاولة توضيحها. فيما يلي قائمة بما يمكنك تعلمه بنفسك ، مع معرفة المبادئ الأساسية:

  • النطاقات
  • ملزم المثال
  • متعدد ملزمة
  • النداءات onready
  • مصدر خارجي
  • نسخة محوها المزالق
  • شكلي Kodein
  • JSR-330 التوافق

حسنا وصلات إلى الوثائق:


شكرا للقراءة ، آمل أن تكون المقالة مفيدة لك!

Source: https://habr.com/ru/post/ar431696/


All Articles