شعار ConstructiCat
CodeBust.
تصفح القسم ▾

استبدال كود النوع بفئات فرعية.

المشكلة

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

الحل

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

##مثال

قبل
class Employee {
  static final int ENGINEER = 0;
  static final int SALESMAN = 1;
  private int type;

  int payAmount() {
    switch (type) {
      case ENGINEER: return monthlySalary;
      case SALESMAN: return monthlySalary + commission;
      default: throw new IllegalStateException();
    }
  }
}
بعد
abstract class Employee {
  static Employee create(int type) {
    switch (type) {
      case ENGINEER: return new Engineer();
      case SALESMAN: return new Salesman();
      default: throw new IllegalArgumentException();
    }
  }
  static final int ENGINEER = 0;
  static final int SALESMAN = 1;
  abstract int payAmount();
}
class Engineer extends Employee {
  int payAmount() { return monthlySalary; }
}
class Salesman extends Employee {
  int payAmount() { return monthlySalary + commission; }
}

##لماذا نعيد الهيكلة

تعتبر تقنية إعادة الهيكلة هذه شكلًا أكثر تعقيدًا من تقنية استبدال كود النوع بفئة.

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

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

##الفوائد

  • حذف كود تدفق التحكم (control flow code). بدلاً من استخدام عبارة switch ضخمة في الفئة الأصلية، انقل الكود إلى الفئات الفرعية المناسبة. يؤدي هذا إلى تحسين الالتزام بـ مبدأ المسؤولية الفردية (Single Responsibility Principle) ويجعل البرنامج أكثر مقروئية بشكل عام.

  • إذا كنت بحاجة إلى إضافة قيمة جديدة لنوع مرمز، فكل ما عليك فعله هو إضافة فئة فرعية جديدة دون لمس الكود الحالي (راجع مبدأ الفتح/الإغلاق - Open/Closed Principle).

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

##كيفية إعادة الهيكلة

  1. استخدم التغليف الذاتي للحقل (Self Encapsulate Field) لإنشاء دالة جلب (getter) للحقل الذي يحتوي على كود النوع.

  2. اجعل باني الفئة الفائقة (superclass constructor) خاصًا (private). أنشئ دالة مصنع ثابتة (static factory method) بنفس المعلمات الخاصة بباني الفئة الفائقة. يجب أن تحتوي على المعلمة التي ستأخذ القيم الأولية للنوع المرمز. بناءً على هذه المعلمة، ستقوم دالة المصنع بإنشاء كائنات من فئات فرعية مختلفة. للقيام بذلك، يجب عليك إنشاء جملة شرطية كبيرة في الكود الخاص بها، ولكن على الأقل، ستكون هي الجملة الشرطية الوحيدة عندما يكون ذلك ضروريًا حقًا؛ وبخلاف ذلك، ستفي الفئات الفرعية وتعدد الأشكال بالغرض.

  3. أنشئ فئة فرعية فريدة لكل قيمة من قيم النوع المرمز. فيها، أعد تعريف دالة جلب النوع المرمز بحيث تعيد القيمة المقابلة للنوع المرمز.

  4. احذف الحقل الذي يحتوي على كود النوع من الفئة الفائقة. واجعل دالة الجلب الخاصة به مجردة (abstract).

  5. الآن بعد أن أصبحت لديك فئات فرعية، يمكنك البدء في نقل الحقول والدوال من الفئة الفائقة إلى الفئات الفرعية المقابلة (بمساعدة إنزال الحقل (Push Down Field) و إنزال الدالة (Push Down Method)).

  6. عندما يتم نقل كل شيء ممكن، استخدم استبدال الشرط بتعدد الأشكال (Replace Conditional with Polymorphism) للتخلص من الشروط التي تستخدم كود النوع مرة واحدة وإلى الأبد.