Kotlin DSL: النظرية والتطبيق

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

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

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



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

بالنسبة لأولئك الذين يرغبون في تشغيل الأمثلة - احتفظ برابط إلى GitHub . ستجد تحت الرابط كل الكود الذي سنقوم بتحليله وتشغيله وكتابته اليوم. افتح الكود وانطلق!



سنناقش اليوم:

  • ما هي اللغات الموجهة للمشكلة؟
  • اللغات المضمنة الموجهة للمشكلات ؛
  • بناء جدول زمني لمؤسسة تعليمية ؛
  • كيف يتم اختبار كل ذلك مع Kotlin.

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

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



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



قليلا عن اختبار جدولة.

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

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

لنكتب اختبار:



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



هذه خطوة أولى جيدة نحو الحد من الازدواجية.



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



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

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



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

اختبار ممتاز


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



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

لغة خاصة بالمجال




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



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



مرة أخرى ، ألق نظرة على مثالنا المثالي وفكر في اللغة التي تختارها. لدينا ثلاثة خيارات.



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



ماذا قدم لنا Kotlin لتطوير لغة موجهة للمشكلة؟

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

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

سوف أذكر مرة أخرى الرابط إلى GitHub ، إذا كنت تريد كتابة المزيد من العروض التوضيحية ، فيمكنك الدخول والتقاط الرمز من الرابط.

تصميم مثالي على Kotlin


دعنا ننتقل إلى تصميم مثالنا ، ولكن بالفعل في Kotlin. الق نظرة على مثالنا:



وسنبدأ في مراحل إعادة بنائه.

لدينا اختبار يتحول إلى دالة في Kotlin ، والتي يمكن تسميتها باستخدام المسافات.



سنضع عليها علامة توضيحية تجريبية ، وهي متاحة لنا من JUnit. في Kotlin ، يمكنك استخدام النموذج المختصر لوظائف الكتابة ، ومن خلال = ، تخلص من الأقواس المتعرجة الإضافية للوظيفة نفسها.

جدول ننتقل إلى كتلة. يحدث نفس الشيء مع الكثير من التصاميم ، لأننا ما زلنا نعمل في Kotlin.



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



يتحول طالبنا إلى كتلة نعمل فيها مع الخصائص ، مع الأساليب ، وسنواصل تحليل ذلك معك.



أخيرا ، المعلمون. هنا لدينا بعض الكتل المتداخلة.



في الكود أدناه ، ننتقل إلى الشيكات. نحتاج إلى التحقق من التوافق مع لغات Java - ونعم ، Kotlin متوافق مع Java.



ترسانة تطوير DSL في Kotlin




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

يوضح الجدول بعض المقارنة بين بناء الجملة الموجه نحو المشكلة والبناء المعتاد المتاح في اللغة.

لامداس في Kotlin


val lambda: () -> Unit = { }

لنبدأ بالآجر الأساسي الذي نمتلكه في Kotlin - هذه هي lambdas.
اليوم ، بنوع لامدا ، سأعني مجرد نوع وظيفي. يُشار إلى Lambdas على النحو التالي: ( ) -> .

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



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



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



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



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



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



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



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



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







إذا كنت ترغب في تمرير سياق كمعلمة ، يمكنك القيام بذلك أيضًا. ومع ذلك ، لا يمكننا نقل السياق تمامًا ، أي أن لامدا مع سياق يتطلب الانتباه! - السياق ، نعم. ماذا يحدث إذا بدأنا في تمرير لامدا مع سياق إلى طريقة ما؟ هنا ننظر مرة أخرى إلى أسلوبنا exec:



إعادة تسميته بطريقة الطالب - لم يتغير شيء:



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



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



من الواضح أننا بحاجة إلى السياق.



هنا نقوم بإنشاء كائن وتشغيل هذه اللمدا عليه.



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



ونتيجة لذلك ، داخل لامدا ، يمكننا الوصول إلى هذه الكلمة الرئيسية - لهذا السبب ، ربما ، هناك لامدا مع السياق.



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



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



التطبيق


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



تلخيص لامداس - لدينا لامداس عادية ، هناك مع السياق ، ويمكن استخدام هذه وغيرها.



عوامل التشغيل


لدى Kotlin مجموعة محدودة من عوامل التشغيل التي يمكنك تجاوزها باستخدام الاصطلاحات والكلمة الأساسية لعامل التشغيل.

دعونا ننظر إلى المعلم وإمكانية الوصول إليه. لنفترض أن المعلم يعمل أيام الاثنين من الساعة 8 صباحًا لمدة ساعة واحدة. نود أن نقول أنه بالإضافة إلى هذه الساعة ، فإنها تعمل من الساعة 13.00 لمدة ساعة واحدة. أود أن أعبر عن هذا باستخدام عامل التشغيل + . كيف يمكن القيام بذلك؟



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



دعنا نكتشف ما هو DayPointer. هذا مؤشر لجدول توفر بعض المعلمين ، واليوم على جدوله الزمني. لدينا أيضًا وظيفة وقت ستحول بعض الصفوف إلى حد ما إلى مؤشرات صحيحة: في Kotlin لدينا فئة IntRange لذلك.

على اليسار يوجد DayPointer ، على اليمين هو الوقت ، ونود أن DayPointer مع عامل التشغيل + . للقيام بذلك ، يمكنك إنشاء عامل التشغيل لدينا في فئة DayPointer . سيستغرق الأمر مجموعة من القيم من النوع Int وإرجاع DayPointer حتى نتمكن من DayPointer DSL الخاص بنا مرارًا وتكرارًا.
الآن ، دعونا نلقي نظرة على التصميم الرئيسي الذي يبدأ به كل شيء ، والذي يبدأ به DSL. تطبيقه مختلف قليلاً ، والآن سنكتشفه.
يمتلك Kotlin مفهومًا فرديًا مدمجًا في اللغة. للقيام بذلك ، بدلاً من الكلمة الأساسية للفئة ، يتم استخدام الكلمة الأساسية object . إذا أنشأنا طريقة داخل قائمة فردية ، فيمكننا الوصول إليها بطريقة لا تحتاج إلى إنشاء مثيل من هذه الفئة مرة أخرى. نشير إليها ببساطة كطريقة ثابتة في الفصل.



إذا نظرت إلى نتيجة فك التجميع (أي ، في بيئة التطوير ، انقر فوق أدوات -> Kotlin -> إظهار Kotlin Bytecode -> Decompile) ، يمكنك مشاهدة تنفيذ المفرد التالي:



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



في الواقع ، تسمح لنا الأقواس باستدعاء طريقة الاستدعاء ولها مُعدِّل عامل تشغيل. إذا مررنا لامدا مع السياق لهذا المشغل ، فإننا نحصل على مثل هذا البناء.



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

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



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



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



يمكن القيام بذلك باستخدام عامل التشغيل: get / set:



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



لذا ، تتحول الأقواس المربعة إلى قراءة ، وتتحول الأقواس المربعة التي نعينها إلى مجموعة.

عرض توضيحي: كائن وعوامل


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

من أجل الراحة ، سأقوم بإيجاز جوهر الفيديو مباشرة في النص.

لنكتب اختبار. لدينا بعض عناصر الجدول الزمني ، وإذا ذهبنا إلى تنفيذه من خلال ctrl + b ، فسوف نرى كل ما تحدثت عنه من قبل.



داخل كائن الجدول ، نريد تهيئة البيانات ، ثم إجراء بعض عمليات التحقق ، وداخل البيانات ، نود أن نقول ما يلي:

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



وهنا يتجلى أحد عيوب لغة Kotlin واللغات الموجهة للمشكلة من حيث المبدأ: من الصعب جدًا معالجة بعض الأشياء التي أنشأناها سابقًا. في هذا العرض التوضيحي ، سأشير إلى كل شيء كمؤشرات ، أي أن rus هي مؤشر 0 ، والرياضيات هي فهرس 2. وبالطبع يقود المعلم شيئًا ما. إنه لا يذهب إلى العمل فقط ، بل يشارك في شيء ما. بالنسبة لقراء هذه المقالة ، أود أن أقدم خيارًا آخر للعنونة ، يمكنك إنشاء علامات فريدة وتخزين الكيانات عليها في الخريطة ، وعندما تحتاج إلى الوصول إلى واحدة منها ، يمكنك دائمًا العثور عليها عن طريق العلامة. استمر في تفكيك DSL.

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

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



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



نواصل كتابة الاختبار أكثر.



هنا ، مرة أخرى ، نستخدم عامل التشغيل get ؛ ليس من السهل الوصول إلى تنفيذه ، ولكن يمكننا القيام بذلك.



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



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



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



يعمل هذا لأن لدينا طريقة مكون ، أي أنها طريقة يتم إنشاؤها بواسطة المترجم بفضل معدِّل البيانات الذي نكتبه قبل الفصل.



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



إذا لم يكن لدينا مُعدِّل بيانات ، فسيكون من الضروري كتابة عامل تشغيل يدويًا يقوم بنفس الشيء.





لذا ، لدينا بعض أساليب ComponNN ، وتتحلل إلى مثل هذه المكالمة:



في جوهره ، هو السكر النحوي على مدى عدة طرق.

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



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

دعونا نلقي نظرة على المعلم ، هذه هي بالضبط إمكانية الوصول هذه ، ونتحدث عنه:



لدينا مدرس ، وطريقة التوافر تسمى (هل لم تفقد خيط الاستدلال حتى الآن؟ :-). من أين أتى؟ أي أن المعلم هو نوع من الكيانات التي لديها فئة ، وهذا هو رمز الأعمال. ولا يمكن أن يكون هناك طريقة إضافية.



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



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



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



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



كيف يعمل؟ لنأخذ مثالاً أبسط - متغير صحيح. دعونا ننشئ وظيفة تمديد لها ، دعنا نسميها shouldBeEqual ، ستفعل شيئًا ، لكن هذا ليس مثيرًا للاهتمام. إذا أضفنا معدّل infix إلى يساره ، فهذا يكفي. يمكنك التخلص من النقاط والأقواس ، ولكن هناك بعض الفروق الدقيقة.



وبناءً على ذلك ، يتم تنفيذ بنية البيانات والتأكيدات فقط ، ويتم تثبيتها معًا.


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



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



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

دعنا ننتقل إلى العرض التوضيحي التالي. من الأفضل مشاهدة الفيديو وعدم قراءة النص.



الآن كل شيء يبدو جاهزًا: الوظيفة التي رأيتها ، تمديد الوظيفة التي رأيتها ، إعلان التدمير جاهز.

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



هناك حالات يمكننا فيها أخذ DSL وإعادة استخدامها داخلها مباشرة ، لكننا لا نريد القيام بذلك. مستخدمنا (ربما مستخدم غير متمرس) يكتب البيانات داخل البيانات ، وهذا لا معنى له. نود أن نمنعه بطريقة أو بأخرى من القيام بذلك.

قبل إصدار Kotlin الإصدار 1.1 ، كان علينا القيام بما يلي: ردًا على حقيقة أن لدينا طريقة بيانات في SchedulingContext ، كان علينا إنشاء طريقة بيانات أخرى في DataContext ، والتي نقبل فيها لامدا (وإن كان بدون تنفيذ) ، يجب أن نضع علامة على هذه الطريقة تعليق توضيحي @Deprecated وإخبار المترجم بعدم تجميع هذا. ترى أن هذه الطريقة تبدأ - لا تجمع. باستخدام هذا النهج ، نحصل على بعض الرسائل المفيدة عندما نكتب كودًا لا معنى له.



بعد الإصدار Kotlin 1.1 ، @DslMarker شرح رائع @DslMarker . هذا التعليق التوضيحي مطلوب لوضع علامة على التعليقات التوضيحية المشتقة. معهم ، بدورنا ، سنحدد اللغات الموجهة للمشكلات. لكل لغة موجهة للمشكلة ، يمكنك إنشاء تعليق توضيحي واحد تقوم بتمييز @DslMarker على كل سياق مطلوب. لم تعد هناك حاجة لكتابة طرق إضافية يجب منعها من الترجمة - كل ذلك يعمل فقط. لا جمعت.

ومع ذلك ، هناك حالة خاصة واحدة عندما نعمل مع نموذج أعمالنا. وعادة ما تكون مكتوبة بلغة جافا. , , . , ? Student . – -, Kotlin .





- , : . , , .



.

  1. , . StudentContext. , . – , , , .
  2. – , , . . StudentContext , IStudent . , Student, IStudent StudentContext. DslMarker , .
  3. : deprecated . , . , . extension-, . .



, , , .



. . , , , . , . @DslMarker, . , @DslMarker, @Deprecated, , .

, :






-, DSL. , DSL . , , , .

, - , , , , - , . ? for — . DSL, , , DSL. this it. , Kotlin 1.2.20 , . , it.

. DSL, --, , . , . , , , - , , . . , - , ..

, . , - – DSL. , Kotlin-, . , DSL , , Kotlin- . -? Gradle-, , , , - . - , , – , DSL.



DSL' , . , . , DSL , , . - – . -, - . , - .
, Kotlin. , , , , , , . (, - , ), , DSL , , , . .

«», . , Kotlin . , , . , — , .
, DSL. , - . DSL, , 10 , , - . DSL – , , .

, . , Telegram: @ivan_osipov , Twitter: @_osipov_ , : i_osipov . .

دقيقة من الدعاية. JPoint — , 19-20 - Joker 2018 — Java-. . , .

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


All Articles