نحن نستخدم روابط ثابتة لخصائص الكائن باستخدام lambdas

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


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


استخدام اسم العقار


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


person.contact.address.city 

المشكلة في هذه الطريقة هي عدم وجود أي سيطرة على تهجي اسم ونوع الملكية بكل ما تتضمنه:


  • لا يوجد عنصر تحكم خطأ في مرحلة الترجمة. يمكنك ارتكاب خطأ في الاسم ، يمكنك تطبيقه على الفئة الخطأ ، لا يتم التحكم في نوع العقار. علينا أن نكتب بالإضافة إلى ذلك اختبارات غبية جدا.
  • لا يوجد دعم من IDE. متعب جدا عند mepe 200+ الحقول. من الجيد أن يكون هناك شهر يونيو لهذا ، والذي يمكن إيقافه.
  • كود اعادة البناء المتطورة. غيِّر اسم الحقل ، وفي الحال ستقع الكثير من الأشياء. سوف IDEs جيدة أيضا إخراج مئات الأماكن التي تحدث كلمة مماثلة.
  • الدعم وتحليل الرمز. نريد أن نرى مكان استخدام الخاصية ، ولكن "Find Usages" لن تظهر السلسلة.

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


  • ملزمة لفئة محددة
  • يحتوي على اسم العقار.
  • لديه نوع

كيف يمكنني الرجوع إلى جامع؟


إنشاء الوكلاء


واحدة من الطرق المثيرة للاهتمام هي proxying (أو moking) الكائنات لاعتراض سلسلة استدعاء getter ، والتي يتم استخدامها في بعض المكتبات: Mockito ، QueryDSL ، BeanPath . حول الأخير على حبري كان هناك مقال من المؤلف.
الفكرة بسيطة للغاية ، ولكنها ليست سهلة التنفيذ (مثال من المقال المذكور).


 Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) ); 

باستخدام إنشاء رمز ديناميكي ، يتم إنشاء فئة بروكسي خاصة ترث من فئة الفول وتعترض جميع استدعاءات getter في السلسلة ، وتُنشئ مسارًا في متغير ThreadLocal. في هذه الحالة ، لا يحدث استدعاء هذه getters الكائن.


في هذه المقالة سننظر في طريقة بديلة.


طريقة الروابط


مع ظهور Java 8 ، جاءت lambdas والقدرة على استخدام مراجع الطريقة. لذلك ، سيكون من الطبيعي أن يكون لديك شيء مثل:


 Person person = … assertEquals("name", $(Person::getName).getPath()); 

تقبل الطريقة $ lambda التالية التي يتم فيها تمرير إشارة getter:


 public interface MethodReferenceLambda<BEAN, TYPE> extends Function<BEAN, TYPE>, Serializable {} ... public static <BEAN, TYPE> BeanProperty<BEAN, TYPE> $(MethodReferenceLambda<BEAN, TYPE> methodReferenceLambda) 

المشكلة هي أنه بسبب الكتابة ، لا توجد طريقة للحصول على أنواع BEAN و TYPE في وقت التشغيل ، كما لا توجد معلومات حول اسم getter: الطريقة التي تسمى "الخارج" هي Function.apply ().


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


 MethodReferenceLambda<Person,String> lambda = Person::getName(); Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); writeMethod.setAccessible(true); SerializedLambda serLambda = (SerializedLambda) writeMethod.invoke(lambda); String className = serLambda.getImplClass().replaceAll("/", "."); String methodName = serLambda.getImplMethodName(); 

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


مكتبة بينريف


يبدو استخدام المكتبة كالتالي:


 Person person = ... //     final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); assertEquals("contact.address.city", personCityProperty.getPath()); 

ولا يتطلب سحر توليد الكود وتبعيات الطرف الثالث. بدلاً من سلسلة getter ، يتم استخدام سلسلة lambda مع الإشارة إلى getters. في نفس الوقت ، يتم احترام سلامة النوع ، كما أن الإكمال التلقائي المستند إلى IDE يعمل جيدًا:


يمكنك استخدام اسم getter في كل من الترميز القياسي (getXXX () / isXXX ()) وغير القياسي (xxx ()). ستحاول المكتبة العثور على المضبط المقابل ، وإذا كانت غائبة ، فسيتم الإعلان عن الخاصية للقراءة فقط.


لتسريع الأداء ، يتم تخزين الخصائص التي تم حلها مؤقتًا ، وعندما تقوم بالاتصال بها مرة أخرى بنفس lambda ، يتم حفظ النتيجة بالفعل.


بالإضافة إلى اسم الخاصية / المسار ، باستخدام كائن BeanPath ، يمكنك الوصول إلى قيمة خاصية الكائن:


 Person person = ... final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); String personCity = personCityProperty.get(person); 

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


من خلال BeanPath ، يمكنك أيضًا تغيير قيمة خاصية الكائن إذا لم تكن للقراءة فقط:


 personCityProperty.set(person, “Madrid”); 

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


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


 final BeanPath<Person, String> personPhonePath = $(Person::getContact).$$(Contact::getPhoneList).$(Phone::getPhone); assertEquals("contact.phoneList.phone", personPhonePath.getPath()); assertEquals(personPhonePath.get(person), person.getContact().getPhoneList() .get(person.getContact().getPhoneList().size()-1).getPhone()); 

يتم استضافة المشروع هنا: https://github.com/throwable/beanref ، تتوفر ثنائيات من مستودع jcenter maven.


Poleznyashki


java.beans.Introspector
تتيح لك الفئة Introspector من Java Java القياسي حل خصائص الصندوق.


اباتشي العموم BeanUtils
أشمل مكتبة للعمل مع Java Beans.


BeanPath
المكتبة المذكورة التي تفعل الشيء نفسه من خلال وكيل.


Objenesis
نحن إنشاء مثيل كائن من أي فئة مع أي مجموعة من الصانعين.


QueryDSL الأسماء المستعارة
استخدام فئات بروكسي لتعيين معايير في QueryDSL


Jinq
مكتبة مثيرة للاهتمام تستخدم lambdas لتعيين المعايير في JPA. وهناك الكثير من السحر: البروكسي ، التسلسل lambdas ، وتفسير bytecode.

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


All Articles