في مملكة معينة ، وليس في حالة "نابضة". تقرير ياندكس

الربيع هو إطار جافا مفتوح المصدر قوي. قررت أن أخبركم بالمهام التي تعتبرها الواجهة الخلفية لـ Spring مفيدة وما هي إيجابياتها وسلبياتها مقارنة بالمكتبات الأخرى: Guice and Dagger 2. فكر في حقن التبعية وانعكاس التحكم - ستتعلم كيفية البدء في دراسة هذه المبادئ.


- مرحبا بالجميع ، اسمي كيرل. اليوم سأتحدث عن Dependency Injection.

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



سأقوم بعمل استطراد صغير - أخبرك بما أعمل عليه ، ما هو مشروعي ، لماذا نستخدم Dependency Injection. بعد ذلك ، سوف أخبركم بكل شيء حول هذا الموضوع ، وقارن Inversion of Control and Dependency Injection ، وأتحدث عن تنفيذه في المكتبات الثلاث الأكثر شهرة.

أنا أعمل في فريق Yandex.Tracker. نحن جعل التماثلية البقالة من جيرا أو Trello. [...] قررنا أن ننتج منتجنا الخاص ، والذي كان داخليًا أولاً. الآن نحن نبيعه. يمكن لكل منكما الدخول وإنشاء جهاز Tracker خاص بك والقيام بمهام - على سبيل المثال ، تعليمية أو تجارية.

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

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



دعونا نرى كيف يمكنك التوصل إلى Dependency Injection بنفسك إذا بدأنا من نقطة الصفر. لنفترض أنني قررت تطوير فئة صغيرة من هذا القبيل حيث سأقوم بإنشاء تذكرة من خلال واجهة برمجة التطبيقات الخاصة بنا. على سبيل المثال ، قم بإنشاء مثيل لفئة TrackerApi. لديها طريقة createTicket التي سنرسل بها بريدي الإلكتروني. سننشئ تذكرة من تحت حسابي باسم: "إعداد تقرير لـ Java Meetup".



دعونا نرى تنفيذ TrackerApi. هنا ، على سبيل المثال ، يمكننا القيام بذلك: إنشاء مثيل httpClient. بعبارات بسيطة ، سنقوم بإنشاء كائن من خلاله سنذهب إلى API. من خلال هذا الكائن سوف ندعو طريقة التنفيذ على ذلك.



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



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



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



لقد قمنا بتحويل المسؤولية إلى مستخدمي الكود ، لذا يجب علينا الآن إنشاء httpClient ، بتمريرها ، على سبيل المثال ، إلى TicketCreator.



هذا ليس جيدًا أيضًا هنا ، لأنه الآن ، عن طريق استدعاء هذه الطريقة ، سننشئ httpClient مرة أخرى في كل مرة.



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

على سبيل المثال ، سيتم إنشاء هذا واحد من تحتي عندما نقوم بإنشائه. والخط الذي نمرره إلى المنشئ هو أيضًا Dependency Injection.



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



الآن ، دعونا نحاول أن نرى شكل رمزنا إذا أردنا قراءة التعليقات في هذه البطاقة بنفس الطريقة ، من تحت لي. هذا عن نفس الرمز. نحن نسمي طريقة getComments على هذه التذكرة.



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



حسنا. الآن دعنا نعيد توجيه كل هذه المعلمات كـ Dependency Injection ، كمعلمات مُنشئ.



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

الآن ، دعونا ننحرف قليلاً وننظر إلى ماهية "انعكاس التحكم" ، لأن العديد من شركاء Dependency Injection معه.



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



ما هي المبادئ الأساسية؟ لا يتم إنشاء الكائنات عن طريق رمز التطبيق ، ولكن بواسطة بعض حاوية IoC. نحن ، كمستخدمين للمكتبة ، لا نفعل شيئًا ، كل شيء يأتي إلينا من تلقاء نفسها. بالطبع ، IoC نسبي. حاوية IoC نفسها تنشئ هذه الكائنات ، وهذا لم يعد قابلاً للتطبيق عليها. قد تعتقد أن IoC لا ينفذ مكتبات DI فقط. أشهر مكتبات Java Servlets و Akka Actors ، والتي تستخدم الآن في Scala وفي Java code.



دعنا نتحدث عن المكتبات. بشكل عام ، تمت بالفعل كتابة الكثير من المكتبات من أجل Java و Kotlin. سأذكر أهمها:

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

دعنا نحاول إعادة كتابة مثالنا في الربيع.



كان لدينا TrackerApi لدينا. أنا لم تشمل المستخدم هنا لفترة قصيرة. لنفترض أننا نحاول في Dependency Injection القيام به من أجل httpClient. للقيام بذلك ، نحتاج إلى إعلان ذلك بتعليق توضيحي. يتم الإعلان عن المكون ، والفئة بأكملها ، وخاصة المنشئ ، مع التعليق التوضيحي Autowired . ماذا يعني هذا لفصل الربيع؟



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



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



TicketCreator نحن ترميز بنفس الطريقة تماما.



و CommentReader أيضا.



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



ما هي إيجابيات وسلبيات؟ زائد الرئيسية - الربيع هو شائع جدا في العالم. القادم زائد وناقص هو autoscanning. لا ينبغي لنا أن نذكر صراحة في أي مكان نريد إضافة حاوية إلى IoC بالإضافة إلى التعليقات التوضيحية على الفئات نفسها. تعليقات توضيحية كافية. والناقص هو نفسه: إذا ، على العكس من ذلك ، نحن نريد السيطرة على ذلك ، فإن Spring لا يوفر لنا هذا. ما لم نتمكن من القول في فريقنا: "لا ، لن نفعل ذلك. يجب أن نصف بوضوح شيء في مكان ما. فقط في التكوين ، كما فعلنا مع الفول.

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



دعنا نلقي نظرة على Guice. هذه مكتبة ، كما قلت ، صنعت بين الربيع الثاني والثالث. الجمال الذي رأيناه لم يكن. كان هناك XML. لإصلاح هذه المشكلة ، وقد كتبه Guice. وهنا يمكنك أن ترى أنه ، على عكس التكوين ، نكتب وحدة نمطية. في ذلك ، نعلن بوضوح الفئات التي نريد وضعها في هذه الوحدة: TrackerAPI ، TrackerTicketCreator وجميع الصناديق الأخرى. التماثل إلى Bean التعليق التوضيحي هنا هو Provides ، مما ينشئ httpClient بنفس الطريقة.



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



قرص صغير مع ما ينتمي إليه.



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

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



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

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



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



هنا عبارة عن لوحة الملخص نفسها - ما الذي تغير أو لم يتغير في حالة الحقن أو الوحدات النمطية.



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

كيف يفعل ذلك؟ باستخدام توليد الشفرة.



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



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

ما هي الجوانب السلبية؟ لديه فرص أقل. إنه مطول أكثر من Guice و Spring.



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

ما هي الاستنتاجات التي يمكن استخلاصها؟ تحتوي Java على الكثير من المكتبات المختلفة لـ DI. وتحتاج إلى فهم لماذا نأخذ واحدة منها. إذا استخدمنا نظام Android ، فلا يوجد خيار بالفعل ، فنحن نستخدم Dagger. إذا ذهبنا إلى العالم الخلفي ، فإننا ننظر بالفعل إلى ما يناسبنا بشكل أفضل. ولأول دراسة لـ Dependency Injection ، يبدو لي أن Guice أفضل من Spring. لا يوجد شيء لزوم لها في ذلك. يمكنك أن ترى كيف يعمل ، ويشعر.

لمزيد من الدراسة ، أقترح أن تتعرف على وثائق جميع هذه المكتبات وتكوين JSR:
- الربيع
- تخمين
- خنجر 2
- JSR-330

شكرا لك

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


All Articles