كيفية حفظ المشروع من الجنيهات الإضافية



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

ما هي المشكلة؟


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

مثال الحياة


يمكن استخدام هذا النهج مع العديد من الحلول المعمارية المختلفة ، لكنني سأعتبره باستخدام VIPER كمثال.

ضع في اعتبارك الطريقة الأكثر شيوعًا في جهاز التوجيه - الطريقة التي تغلق الشاشة:

func close() { self.transitionHandler.dismiss(animated: true, completion: nil) } 

إنه موجود في العديد من أجهزة التوجيه ، ومن الأفضل كتابته مرة واحدة فقط.

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

ما هو أفضل من الميراث؟ بالطبع التكوين.

يمكنك إنشاء فصل منفصل للطريقة التي تغلق الشاشة وإضافتها إلى كل جهاز توجيه مطلوب فيها:

 struct CloseRouter { let transitionHandler: UIViewController func close() { self.transitionHandler.dismiss(animated: true, completion: nil) } } 

لا يزال يتعين علينا إعلان هذه الطريقة في بروتوكول الإدخال للموجه وتنفيذها في جهاز التوجيه نفسه:

 protocol SomeRouterInput { func close() } class SomeRouter: SomeRouterInput { var transitionHandler: UIViewController! lazy var closeRouter = { CloseRouter(transitionHandler: self. transitionHandler) }() func close() { self.closeRouter.close() } } 

اتضح أن الكثير من التعليمات البرمجية التي تعمل ببساطة على تحويل المكالمة إلى طريقة الإغلاق. مبرمج جيد كسول لن يقدر.

حل بروتوكول


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

هكذا ستبدو:

 protocol CloseRouterTrait { var transitionHandler: UIViewController! { get } func close() } extension CloseRouterTrait { func close() { self.transitionHandler.dismiss(animated: true, completion: nil) } } 

السؤال هو ، لماذا تظهر كلمة سمة في اسم البروتوكول؟ إنه أمر بسيط - يمكنك تحديد أن هذا البروتوكول يطبق أساليبه في التمديد ويجب استخدامه كخليط لنوع آخر لتوسيع وظائفه.

الآن ، دعنا نرى كيف سيبدو استخدام مثل هذا البروتوكول:

 class SomeRouter: CloseRouterTrait { var transitionHandler: UIViewController! } 

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

ما هو غير المعتاد في هذا النهج؟


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

هنا قائمتي:

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

من VIPER إلى MVP


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

 class SomeRouter: CloseRouterTrait, OtherRouterTrait { var transitionHandler: UIViewController! } class SomeInteractor: SomeInteractorTrait { var someService: SomeServiceInput! } 

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

شيء من هذا القبيل:

 class SomePresenter: CloseRouterTrait, OtherRouterTrait, SomeInteractorTrait, OtherInteractorTrait { var transitionHandler: UIViewController! var someService: SomeSericeInput! } 

نعم ، تُفقد القدرة على تنفيذ جهاز التوجيه والمُتفاعل كاعتماد ، ولكن في بعض الحالات يكون هذا هو الحال.

العيب الوحيد هو TransHandler = UIViewController. ووفقًا لقواعد مقدم VIPER ، لا ينبغي معرفة أي شيء عن طبقة العرض وكيف يتم تنفيذها باستخدام التقنيات. يتم حل هذا في هذه الحالة ببساطة - يتم "إغلاق" طرق الانتقال من UIViewController بواسطة البروتوكول ، على سبيل المثال ، TransitionHandler. حتى يتفاعل المقدم مع التجريد.

تغيير سلوك السمات


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

كمثال ، خذ متفاعلًا بسيطًا مع طريقة تؤدي إلى طلب شبكة:

 protocol SomeInteractorTrait { var someService: SomeServiceInput! { get } func performRequest(completion: @escaping (Response) -> Void) } extension SomeInteractorTrait { func performRequest(completion: @escaping (Response) -> Void) { someService.performRequest(completion) } } 

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

 protocol Mock {} extension SomeInteractorTrait where Self: Mock { func performRequest(completion: @escaping (Response) -> Void) { completion(MockResponse()) } } 

هنا ، تم تغيير تنفيذ أسلوب PerformRequest للأنواع التي تطبق بروتوكول Mock. تحتاج الآن إلى تنفيذ بروتوكول Mock للفئة التي ستقوم بتطبيق SomeInteractor:

 class SomePresenter: SomeInteractorTrait, Mock { // Implementation } 

بالنسبة لفئة SomePresenter ، سيتم استدعاء تنفيذ أسلوب PerformRequest ، الموجود في الامتداد ، حيث يفي Self بروتوكول Mock. يجدر إزالة بروتوكول Mock وسيتم تنفيذ طريقة PerformRequest من الامتداد المعتاد إلى SomeInteractor.

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

لتلخيص


في الختام ، تجدر الإشارة إلى إيجابيات وسلبيات هذا النهج ، وفي الحالات ، في رأيي ، يجدر استخدامه.

لنبدأ بالسلبيات:

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

الجوانب الإيجابية لهذا النهج هي كما يلي:

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

يعتبر اعتبار هذا القرار جيدًا أم لا أمرًا خاصًا للجميع. كانت تجربتنا مع هذا النهج إيجابية وحل المشاكل.

هذا كل شيء!

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


All Articles