ذات مرة في بنك واحد بعيد ...
يوم جيد ، هبر. واليوم ، أخيرًا ، وصلت يدي إلى هنا مرة أخرى للكتابة. ولكن على عكس الدروس السابقة - المقالات اليوم أود أن أشارك تجربتي وأظهر قوة آلية مثل الأدوية الجنسية ، والتي ، إلى جانب سحر الربيع ، تصبح أقوى. أريد أن أحذرك على الفور من أجل فهم المقالة ، تحتاج إلى معرفة أساسيات النابض ولديك أفكار حول الأدوية الجنيسة التي هي أكثر من مجرد "Generics ، حسنًا ، ما نشير إليه في علامات الاقتباس في ArrayList".
الحلقة 1:
بادئ ذي بدء ، كانت المهمة التي كان لدي في العمل شيء من هذا القبيل: كان هناك عدد كبير من التحويلات المالية مع عدد معين من المجالات المشتركة. بالإضافة إلى ذلك ، تتوافق كل ترجمات مع الفئات - طلبات النقل من دولة إلى أخرى وإعادة التوجيه إلى واجهة برمجة تطبيقات أخرى. وفقًا لذلك ، كان هناك عمال بناء شاركوا في التحويل.
لقد قمت بحل المشكلة في المجالات المشتركة ببساطة عن طريق الميراث. لذا حصلت على دروس:
public class Transfer { private TransferType transferType; ... } public enum TransferType { INTERNAL, SWIFT, ...; } public class InternalTransfer extends Transfer { ... } public class BaseRequest { ... } public class InternalRequest extends BaseRequest { ... } ...
الحلقة 2:
ثم كانت هناك مشكلة مع وحدات التحكم - يجب أن يكون لديهم جميعًا نفس الأساليب - checkTransfer ، الموافقة على النقل ، إلخ. هنا ، للمرة الأولى ، وليس المرة الأخيرة ، أصبحت الأدوية البديلة في متناول اليدين: لقد صنعت وحدة تحكم عامة بالطرق اللازمة ، ورثت الباقي منها:
@AllArgsConstructor public class TransferController<T extends Transfer> { private final TransferService<T> service; public CheckResponse checkTransfer(@RequestBody @Valid T payment) { return service.checkTransfer(payment); } ... } public class InternalTransferController extends TransferController<InternalTransfer> { public InternalTransferController(TransferService<InternalTransfer> service) { super(service); } }
حسنًا ، في الواقع الخدمة:
public interface TransferService<T extends Transfer> { CheckResponse checkTransfer(T payment); ApproveResponse approveTransfer(T payment); ... }
وبالتالي ، تم تقليل مشكلة لصق النسخ فقط إلى استدعاء البناء الفائق ، وفي الخدمة فقدناها بشكل عام.
لكن!
الحلقة 3:
لا تزال هناك مشكلة داخل الخدمة:
اعتمادًا على نوع النقل ، كان يجب استدعاء العديد من البنائين:
RequestBuilder builder; switch (type) { case INTERNAL: { builder = beanFactory.getBean(InternalRequestBuilder.class); break; } case SWIFT: { builder = beanFactory.getBean(SwiftRequestBuilder.class); break; } default: { log.info("Unknown payment type"); throw new UnknownPaymentTypeException(); } }
واجهة البناء المعممة:
public interface RequestBuilder<T extends BaseRequest, U extends Transfer> { T createRequest(U transfer); }
جاءت طريقة المصنع للتحسين هنا ، ونتيجة لذلك ، فإن التبديل / الحالة في فئة منفصلة. يبدو أن الأمر أفضل ، لكن المشكلة ظلت كما هي - عندما تضيف ترجمة جديدة ، عليك تعديل الكود ، ولم يناسبني التبديل / الحالة الضخمة.
الحلقة 4:
ما هو المخرج؟ في البداية ، حدث لي تحديد نوع الترجمات حسب اسم الفصل واستدعاء المنشئ المطلوب باستخدام الانعكاس ، مما سيجبر المطورين الذين سيعملون مع المشروع على تلبية متطلبات معينة لأسماء فصولهم. ولكن كان هناك حل أفضل. بعد أن تساءلت ، يمكنك الوصول إلى استنتاج مفاده أن الجانب الرئيسي لمنطق الأعمال في التطبيق هو الترجمات نفسها. أي أنه إذا لم يكن هناك أي شيء ، فلن يكون هناك شيء آخر. فلماذا لا تربط كل شيء؟ يكفي تعديل فصولنا قليلا. ومرة أخرى تأتي الأدوية العامة للإنقاذ.
فئات الطلب:
public class BaseRequest<T extends Transfer> { ... } public class InternalRequest extends BaseRequest<InternalTransfer> { ... }
وواجهة الباني:
public interface RequestBuilder<T extends Transfer> { BaseRequest<T> createRequest(T transfer); }
وهنا أصبح الأمر أكثر إثارة للاهتمام. نحن نواجه ميزة للأدوية التي لم يتم ذكرها في أي مكان تقريبًا ويتم استخدامها بشكل رئيسي في الأطر والمكتبات. في الواقع ، يمكن لـ BaseRequest استبدال وريثه ، والذي يتوافق مع النوع T ، أي:
public class InternalRequestBuilder implements RequestBuilder<InternalTransfer> { @Override public InternalRequest createRequest(InternalTransfer transfer) { return InternalRequest.builder() ... .build(); } }
في الوقت الحالي ، حققنا تحسينًا جيدًا في بنية التطبيقات لدينا. لكن مشكلة التبديل / الحالة لم يتم حلها بعد. أم ...؟
الحلقة 5:
هنا يأتي دور سحر الربيع.
والحقيقة هي أن لدينا الفرصة للحصول على مصفوفة من أسماء الحاوية المقابلة للنوع المطلوب باستخدام طريقة
getBeanNamesForType (نوع ResolvableType) . وفي فئة ResolveableType ، توجد طريقة ثابتة لـ
ClassWithGenerics (Class <؟> Clazz ، Class <؟> ... genics) ، حيث تحتاج إلى تمرير الفئة (الواجهة) كمعلمة أولى ، والتي تستخدم المعلمة الثانية كمرجع عام وترجع النوع المقابل. م ه:
ResolvableType type = ResolvableType.forClassWithGenerics(RequestBuilder.class, transfer.getClass());
إرجاع ما يلي:
RequestBuilder<InternalTransfer>
والآن أكثر سحرًا قليلاً - والحقيقة هي أنه إذا قمت بتزويد ورقة بواجهة عامة ، فسيتم تضمين جميع عمليات التنفيذ فيها:
private final List<RequestBuilder<T>> builders;
علينا فقط أن نراجعه ونعثر على الخيار المناسب باستخدام فحص المثيل:
builders.stream() .filter(b -> type.isInstance(b)) .findFirst() .get();
على غرار هذا الخيار ، لا تزال هناك فرصة للتطبيق التلقائي ApplicationContext أو BeanFactory ، واستدعاء طريقة getBeanNamesForType () عليها لتمرير نوعنا كمعلمة. لكن هذا يعتبر علامة على الذوق السيئ وهذه العمارة ليست ضرورية (شكر خاص لـ
zolt85 للتعليق).
نتيجة لذلك ، تتخذ طريقة المصنع الشكل التالي:
@Component @AllArgsConstructor public class RequestBuildersFactory<T extends Transfer> { private final List<RequestBuilder<T>> builders; public BaseRequest<T> transferToRequest(T transfer) { ResolvableType type = ResolvableType.forClassWithGenerics(RequestBuilder.class, transfer.getClass()); RequestBuilder<T> builder = builders.stream() .filter(b -> type.isInstance(b)) .findFirst() .get(); return builder.createRequest(transfer, stage); } }
الحلقة 6: الخاتمة
وبالتالي ، لدينا إطار عمل صغير بهندسة معمارية مدروسة جيدًا ، مما يلزم جميع المطورين بالالتزام به. والأهم من ذلك أننا تخلصنا من التبديل / الحالة المرهقة ، ولن تؤدي إضافة ترجمات جديدة إلى التأثير على الفصول الحالية بأي شكل من الأشكال ، وهي أخبار جيدة.
ملاحظة:لا يشجع هذا المقال على استخدام الأدوية الجنيسة حيثما كان ذلك ممكنًا ومستحيلًا ، ولكن بمساعدته ، أريد مشاركة الآليات والبنى القوية التي تسمح لك بإنشائها.
شكر وتقدير:شكر خاص
لسلطان سوي ، الذي بدونه لم تكن هذه العمارة
ستوضع في الذهن ، وعلى الأرجح ، لم يكن هذا المقال.
المراجع:كود مصدر جيثب