Refactoring/replace conditional with polymorphism

من موسوعة حسوب
مراجعة 15:55، 18 ديسمبر 2018 بواسطة Khaled-yassin (نقاش | مساهمات) (أنشأ الصفحة ب'  تبديل الشرطيات بالتعدديّة الشكليّة (Replace Conditional with Polymorphism) == المشكلة == وجود شَرطية تنفذ إجر...')
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

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

المشكلة

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

الحل

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

مثال

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

في لغة 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")

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

في لغة 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. كرر الاستبدال حتى تصبح الشرطيات فارغة. ثم احذف الشرطية وعرِّف نبذة مختصرة عن التابع.

انظر أيضًا

مصادر