تبديل الشرطيات بالتعدديّة الشكليّة (Replace Conditional with Polymorphism)

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

المشكلة

وجود شروط تنفِّذ إجراءات مختلفة اعتمادًا على نوع الكائن أو خصائصه.

الحل

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

مثال

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

الصنف Bird يحتوي على التابع getSpeed الذي باستعمال البنية الشرطية switch من النوع type لحساب السرعة بناءً على قيمته:

في لغة Java:

class Bird {
  //...
  double getSpeed() {
    switch (type) {
      case EUROPEAN:
        return getBaseSpeed();
      case AFRICAN:
        return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return (isNailed) ? 0 : getBaseSpeed(voltage);
    }
    throw new RuntimeException("Should be unreachable");
  }
}

في لغة C#‎:

public class Bird 
{
  //...
  public double GetSpeed() 
  {
    switch (type) 
    {
      case EUROPEAN:
        return GetBaseSpeed();
      case AFRICAN:
        return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return isNailed ? 0 : GetBaseSpeed(voltage);
      default:
        throw new Exception("Should be unreachable");
    }
  }
}

في لغة PHP:

class Bird {
  ...
  function getSpeed() {
    switch ($this->type) {
      case EUROPEAN:
        return $this->getBaseSpeed();
      case AFRICAN:
        return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts;
      case NORWEGIAN_BLUE:
        return ($this->isNailed) ? 0 : $this->getBaseSpeed($this->voltage);
    }
    throw new Exception("Should be unreachable");
  }
  ...
}

في لغة Python:

class Bird:
    #...
    def getSpeed(self):
        if self.type == EUROPEAN:
            return self.getBaseSpeed();
        elif self.type == AFRICAN:
            return self.getBaseSpeed() - self.getLoadFactor() * self.numberOfCoconuts;
        elif self.type == NORWEGIAN_BLUE:
            return 0 if isNailed else self.getBaseSpeed(self.voltage)
        else:
            raise Exception("Should be unreachable")

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

حذفنا البنية الشرطية switch وعرَّفنا أصنافًا متفرعةً من الصنف Bird تعيد تعريف كل منها التابع ()getSpeed بناءً على القيمة التي ستُستدعَى معه مستقبلًا:

في لغة Java:

abstract class Bird {
  //...
  abstract double getSpeed();
}

class European extends Bird {
  double getSpeed() {
    return getBaseSpeed();
  }
}
class African extends Bird {
  double getSpeed() {
    return getBaseSpeed() - getLoadFactor() * numberOfCoconuts;
  }
}
class NorwegianBlue extends Bird {
  double getSpeed() {
    return (isNailed) ? 0 : getBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.getSpeed();

في لغة C#‎:

public abstract class Bird 
{
  //...
  public abstract double GetSpeed();
}

class European: Bird 
{
  public override double GetSpeed() 
  {
    return GetBaseSpeed();
  }
}
class African: Bird 
{
  public override double GetSpeed() 
  {
    return GetBaseSpeed() - GetLoadFactor() * numberOfCoconuts;
  }
}
class NorwegianBlue: Bird
{
  public override double GetSpeed() 
  {
    return isNailed ? 0 : GetBaseSpeed(voltage);
  }
}

// Somewhere in client code
speed = bird.GetSpeed();

في لغة PHP:

abstract class Bird {
  ...
  abstract function getSpeed();
  ...
}

class European extends Bird {
  function getSpeed() {
    return $this->getBaseSpeed();
  }
}
class African extends Bird {
  function getSpeed() {
    return $this->getBaseSpeed() - $this->getLoadFactor() * $this->numberOfCoconuts;
  }
}
class NorwegianBlue extends Bird {
  function getSpeed() {
    return ($this->isNailed) ? 0 : $this->getBaseSpeed($this->voltage);
  }
}

// Somewhere in Client code.
$speed = $bird->getSpeed();

في لغة Python:

class Bird:
    #...
    def getSpeed(self):
        pass

class European(Bird):
    def getSpeed(self):
        return self.getBaseSpeed()
    
    
class African(Bird):
    def getSpeed(self):
        return self.getBaseSpeed() - self.getLoadFactor() * self.numberOfCoconuts


class NorwegianBlue(Bird):
    def getSpeed():
        return 0 if self.isNailed else self.getBaseSpeed(self.voltage)

# Somewhere in client code
speed = bird.getSpeed()

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

يمكن أن تساعد تقنية إعادة التصميم هذه إذا احتوت الشيفرة البرمجية على عوامل تُنفِّذ مهامًا مختلفةً تختلف استنادًا إلى:

  • صنف الكائن أو الواجهة التي ينفذها.
  • قيمة حقل الكائن.
  • نتيجة استدعاء أحد توابع الكائن.

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

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

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

آلية الحل

التحضير لإعادة التصميم

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

  • تبديل رموز الأنواع بالأصناف الفرعية (Replace Type Code with Subclasses). ستُنشأ الأصناف الفرعية لجميع قيم خاصية معينة للكائن. هذا الأسلوب بسيط ولكنه أقل مرونة لأنه لا يمكنك إنشاء أصناف فرعية للخصائص الأخرى للكائن.
  • تبديل رموز الأنواع بالحالة/الاستراتيجية (Replace Type Code with State/Strategy). سيُخصِّص صنف لخاصية معينة للكائن وستُنشَأ أصناف فرعية منه لكل قيمة من الخاصية. سيحتوي الصنف الحالي على مراجع إلى الكائنات من هذا النوع وسيفوض إليها التنفيذ.

تفترض الخطوات التالية أنك أنشأت التسلسل الهرمي بالفعل.

خطوات إعادة التصميم

  1. إذا كانت البنية الشرطية في تابع ينفذ إجراءات أخرى، فقم بإجراء استخراج التوابع.
  2. لكل صنف تسلسل هرمي فرعي، أعِد تعريف التابع الذي يحتوي على البنية الشرطية وانسخ الشيفرة البرمجية للفرع الشرطي المقابل لذلك الموقع.
  3. احذف هذا الفرع من البنية الشرطية.
  4. كرر الاستبدال حتى تصبح فروع البنية الشرطية فارغةً. ثم احذف هذه البنية وعرِّف نبذة مختصرة عن التابع.

انظر أيضًا

مصادر