تبديل رموز الأنواع بالأصناف الفرعية (Replace Type Code with Subclasses)

من موسوعة حسوب

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

المشكلة

يؤثر النوع المُرمَّز على سلوك البرنامج (تُطلِق قيم هذا الحقل رموز مختلفة في الشرطيات).

الحل

إنشاء أصناف فرعية لكل قيمة من النوع المُرمَّز. ثم استخراج السلوكيات ذات الصلة من الصنف الأصلي إلى هذه الأصناف الفرعية. وتبديل رموز التحكم في التدفق بالتعدديّة الشكليّة (polymorphism).

مثال

قبل إعادة التصميم

احتواء الصنف Employee على حقول ذات أنواع مرمزة مثل ENGINEER و SALESMAN.

احتواء الصنف Employee على أنواع مرمزة مثل ENGINEER و SALESMAN.
احتواء الصنف Employee على أنواع مرمزة مثل ENGINEER و SALESMAN.

بعد إعادة التصميم

إنشاء صنفان فرعيان للحقل Engineer و Salesman ليحل كل منهما مكان النوع المُرمَّز المقابل له في الصنف Employee الأب.

إنشاء أصناف فرعية للحقل Engineer و Salesman بدلًا من استعمال النوع المُرمَّز لكليهما.
إنشاء أصناف فرعية للحقل Engineer و Salesman بدلًا من استعمال النوع المُرمَّز لكليهما.

لم إعادة التصميم؟

تُعد تقنية إعادة التصميم هذه تطورًا أكثر تعقيدًا من تبديل رموز الأنواع بالأصناف.

كما هو الحال مع تابع إعادة التصميم الأول، يوجد مجموعة من القيم البسيطة التي تشكل كافة القيم المسموح بها للحقل. على الرغم من تحديد هذه القيم غالبًا كثوابت ولها أسماء مفهومة، يجعل استخدامها شيفرتك البرمجية أكثر عرضةً للخطأ لأنها لا تزال بدائية في التأثير. فعلى سبيل المثال، إذا وُجِد تابعٌ يقبل إحدى هذه القيم كمُعامل. في لحظة معينة بدلًا من أن يتلقى التابعُ الثابتَ USER_TYPE_ADMIN بالقيمة "ADMIN"، فسيتلقى نفس السلسلة النصية بالحروف الصغيرة ("admin")، والتي ستسبب تنفيذ شيء آخر لم يقصده المُبرمِج.

نحن نتعامل هنا مع رمز التحكم في التدفق مثل البنى الشرطية if و switch و ‎?:‎. وبعبارة أخرى، تُستخدم الحقول ذات القيم المُرمَّزة (مثل ‎$user->type === self::USER_TYPE_ADMIN) داخل شروط هذه العوامل. إذا كان لنا أن نستخدم تبديل رموز الأنواع بالأصناف هنا، سيكون من الأفضل نقل كل بُنى التحكم في التدفق هذه إلى صنف مسؤول عن نوع البيانات. في نهاية المطاف، سيُنشئ هذا بالطبع صنف نوع مشابهه جدًا للصنف الأصلي، ومع نفس المشاكل أيضًا.

فوائد تطبيق الحل

  • حذف رمز تدفق التحكم. نقل الرموز إلى الأصناف الفرعية المناسبة بدلًا من استعمال switch بشكل ضخم في الصنف الأصلي يزيد من الالتزام بمبدأ المسؤولية الفردية (Single Responsibility Principle) ويجعل البرنامج أكثر قابلية للقراءة.
  • إذا دعت الحاجة إلى إضافة قيمة جديدة لنوع مُرمَّز، فيجب إضافة صنف فرعي جديد دون المساس بالرمز الحالي (راجع مبدأ مفتوح/مغلق).
  • يُسمح بالتلميح بنوع التوابع والحقول على مستوي لغة البرمجة وذلك باستبدال رموز الأنواع بالأصناف. ولن يكون هذا ممكنًا باستخدام القيم الرقمية البسيطة أو السلاسل النصية المضمنة في النوع المُرمَّز.

متى يترك هذا الحل؟

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

آلية الحل

  1. استخدم التغليف الداخلي للحقل لإنشاء مُتلقي للحقل الذي يحتوي على رمز النوع.
  2. اجعل مُنشئ الصنف الأعلى (superclass) خاصًا. ثم أنشئ تابعًا منتجًا (static factory method) ساكن بنفس مُعاملات مُنشئ الصنف الأب. ويجب أن يحتوي على المعامل الذي سيتخذ القيم الابتدائية للنوع المُرمَّز. اعتمادًا على هذا المعامل، سيُنشئ تابعُ التصميم كائنات بأصناف فرعية مختلفة. يجب إنشاء شرطية كبيرة في الشيفرة البرمجية للقيام بذلك، ولكن عندما تكون ضرورية حقًا، ستكون على الأقل هي الوحيدة؛ وإلا ستقوم الأصناف الفرعية والتعددية الشكلية بذلك.
  3. أنشئ صنفًا فرعيًا فريدًا لكل قيمة من النوع المُرمَّز. أعِد تعريف مُتلقي النوع المُرمََّز فيه بحيث يُعيد القيمة المقابلة للنوع المُرمََّز.
  4. احذف الحقل ذا رمز النوع من الصنف الأب. اجعل مُتلقيه مجردًا (abstract).
  5. الآن وقد أصبح لديك أصناف فرعية؛ يمكن البدء في نقل الحقول والتوابع من الصنف الأب إلى الأصناف الفرعية المناظرة (باستخدام حقل الدفع إلى أسفل وتابع الدفع إلى أسفل).
  6. عند نقل كل ما يمكن نقله، استخدم تبديل الشرطيات بالتعدديّة الشكليّة من أجل التخلص من الشروط التي تستخدم رمز النوع مرةً واحدةً وإلى الأبد.

انظر أيضًا

مصادر