الفرق بين المراجعتين ل"Refactoring/replace conditional with polymorphism"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
(أنشأ الصفحة ب'  تبديل الشرطيات بالتعدديّة الشكليّة (Replace Conditional with Polymorphism) == المشكلة == وجود شَرطية تنفذ إجر...')
 
ط (مراجعة وتدقيق.)
 
(مراجعة متوسطة واحدة بواسطة مستخدم واحد آخر غير معروضة)
سطر 1: سطر 1:
 
+
<noinclude>{{DISPLAYTITLE: تبديل الشرطيات بالتعدديّة الشكليّة (Replace Conditional with Polymorphism)}}</noinclude>
 تبديل الشرطيات بالتعدديّة الشكليّة (Replace Conditional with Polymorphism)
 
 
 
 
== المشكلة ==
 
== المشكلة ==
وجود شَرطية تنفذ إجراءات مختلفة اعتمادًا على نوع الكائن أو خصائصه.
+
وجود شروط تنفِّذ إجراءات مختلفة اعتمادًا على نوع الكائن أو خصائصه.
  
 
== الحل ==
 
== الحل ==
إنشاء أصناف فرعية مطابقة لفروع الشرطية. ويُنشأ فيها تابع مشترك وتُنقل إليه الشيفرة البرمجية من الفرع المقابل من الشرطية. ثم تُستبدل الشرطية باستدعاء التابع المناسب. وينتج عن ذلك تنفيذ سليم يتحقق من خلال التعدديّة الشكليّة اعتمادًا على صنف الكائن.
+
إنشاء أصناف فرعية مطابقة لفروع البنية الشرطية. ويُنشأ فيها تابع مشترك وتُنقل إليه الشيفرة البرمجية من الفرع المقابل من الشرطية. ثم تُستبدل البنية الشرطية باستدعاء التابع المناسب. وينتج عن ذلك تنفيذ سليم يتحقق من خلال التعدديّة الشكليّة اعتمادًا على صنف الكائن.
 +
 
 +
== مثال ==
  
=== مثال ===
+
=== قبل إعادة التصميم ===
 +
الصنف <code>Bird</code> يحتوي على التابع <code>getSpeed</code> الذي باستعمال البنية الشرطية <code>switch</code> من النوع <code>type</code> لحساب السرعة بناءً على قيمته:
  
==== قبل إعادة التصميم ====
 
 
في لغة Java:<syntaxhighlight lang="java">
 
في لغة Java:<syntaxhighlight lang="java">
 
class Bird {
 
class Bird {
سطر 75: سطر 75:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==== بعد إعادة التصميم ====
+
=== بعد إعادة التصميم ===
 +
حذفنا البنية الشرطية <code>switch</code> وعرَّفنا أصنافًا متفرعةً من الصنف <code>Bird</code> تعيد تعريف كل منها التابع <code>()getSpeed</code> بناءً على القيمة التي ستُستدعَى معه مستقبلًا:
 +
 
 
في لغة Java:<syntaxhighlight lang="java">
 
في لغة Java:<syntaxhighlight lang="java">
 
abstract class Bird {
 
abstract class Bird {
سطر 181: سطر 183:
  
 
== لم إعادة التصميم؟ ==
 
== لم إعادة التصميم؟ ==
يمكن أن تساعد تقنية إعادة التصميم هذه إذا احتوت الشيفرة البرمجية علي عوامل تُنفِّذ مهام مختلفة والتي تختلف استنادًا إلى:
+
يمكن أن تساعد تقنية إعادة التصميم هذه إذا احتوت الشيفرة البرمجية على عوامل تُنفِّذ مهامًا مختلفةً تختلف استنادًا إلى:
 
* صنف الكائن أو الواجهة التي ينفذها.
 
* صنف الكائن أو الواجهة التي ينفذها.
 
* قيمة حقل الكائن.
 
* قيمة حقل الكائن.
 
* نتيجة استدعاء أحد توابع الكائن.
 
* نتيجة استدعاء أحد توابع الكائن.
إذا ظهرت خاصية أو نوع كائن جديدين، ستحتاج إلى البحث عن الشيفرة البرمجية وإضافتها في كل الشرطيات المتشابهة. وبالتالي ستتضاعف فائدة هذه التقنية إذا كان هناك العديد من الشرطيات المتناثرة في جميع توابع الكائن.
+
إذا ظهرت خاصية أو نوع كائن جديدين، فستحتاج إلى البحث عن الشيفرة البرمجية وإضافتها في كل الشروط المتشابهة. وبالتالي ستتضاعف فائدة هذه التقنية إذا كان هناك العديد من الشروط المتناثرة في جميع توابع الكائن.
  
 
== فوائد تطبيق الحل ==
 
== فوائد تطبيق الحل ==
* تلتزم هذه التقنية بمبدأ قل-ولا-تسال: بدلًا من سؤال كائن حول حالته ثم تنفيذ الإجراءات استنادا إلى هذا، فانه من الأسهل كثيرًا توجيه الكائن ببساطة بما يحتاج إلى عمله، والسماح له بأن يقرر لنفسه كيفية القيام بذلك.
+
* تلتزم هذه التقنية بمبدأ ''قل ولا تسأل''؛ فبدلًا من سؤال كائن حول حالته ثمَّ تنفيذ الإجراءات استنادًا إليها، فإنَّه من الأسهل كثيرًا توجيه الكائن ببساطة إلى ما يحتاج إلى عمله، والسماح له بأنَّ يقرر لنفسه كيفية القيام بذلك.
* إزالة الشيفرة البرمجية المكررة. إذ يمكنك التخلص من العديد من الشرطيات التي تكاد أن تكون متطابقة.
+
* إزالة الشيفرة البرمجية المكررة. إذ يمكنك التخلص من العديد من الشروط التي تكاد أن تكون متطابقة.
* إذا كنت بحاجة إلى إضافة متغير تنفيذ جديد، كل ما عليك القيام به هو إضافة صنف فرعي جديد دون المساس بالشيفرة البرمجية الموجودة (مبدأ مفتوح/مغلق).
+
* إذا كنت بحاجة إلى إضافة متغير تنفيذ جديد، كل ما عليك القيام به هو إضافة صنف فرعي جديد دون المساس بالشيفرة البرمجية الموجودة (مبدأ ''مفتوح/مغلق'').
  
 
== آلية الحل ==
 
== آلية الحل ==
  
 
=== التحضير لإعادة التصميم ===
 
=== التحضير لإعادة التصميم ===
بالنسبة لتقنية إعادة التصميم هذه، يجب أن يكون لديك تسلسل هرمي جاهز للأصناف التي ستحتوي على سلوكيات بديلة. إذا لم يكن لديك تسلسل هرمي مثل هذا، أنشئ واحد. و ستساعدك تقنيات أخرى على تحقيق هذا:
+
بالنسبة لتقنية إعادة التصميم هذه، يجب أن يكون لديك تسلسل هرمي جاهز للأصناف التي ستحتوي على سلوكيات بديلة. إذا لم يكن لديك تسلسل هرمي مثل هذا، أنشئ واحدًا. و ستساعدك تقنيات أخرى على تحقيق هذا منها:
 
* [[Refactoring/replace type code with subclasses|تبديل رموز الأنواع بالأصناف الفرعية]] (Replace Type Code with Subclasses). ستُنشأ الأصناف الفرعية لجميع قيم خاصية معينة للكائن. هذا الأسلوب بسيط ولكنه أقل مرونة لأنه لا يمكنك إنشاء أصناف فرعية للخصائص الأخرى للكائن.
 
* [[Refactoring/replace type code with subclasses|تبديل رموز الأنواع بالأصناف الفرعية]] (Replace Type Code with Subclasses). ستُنشأ الأصناف الفرعية لجميع قيم خاصية معينة للكائن. هذا الأسلوب بسيط ولكنه أقل مرونة لأنه لا يمكنك إنشاء أصناف فرعية للخصائص الأخرى للكائن.
* [[Refactoring/replace type code with subclasses|تبديل رموز الأنواع بالحالة/الاستراتيجية]] (Replace Type Code with State/Strategy). سيُخصص صنف لخاصية معينة للكائن وستُنشأ أصناف فرعية منه لكل قيمة من الخاصية. سيحتوي الصنف الحالي على مراجع إلى الكائنات من هذا النوع وسيفوض إليها التنفيذ.
+
* [[Refactoring/replace type code with subclasses|تبديل رموز الأنواع بالحالة/الاستراتيجية]] (Replace Type Code with State/Strategy). سيُخصِّص صنف لخاصية معينة للكائن وستُنشَأ أصناف فرعية منه لكل قيمة من الخاصية. سيحتوي الصنف الحالي على مراجع إلى الكائنات من هذا النوع وسيفوض إليها التنفيذ.
 
تفترض الخطوات التالية أنك أنشأت التسلسل الهرمي بالفعل.
 
تفترض الخطوات التالية أنك أنشأت التسلسل الهرمي بالفعل.
  
 
=== خطوات إعادة التصميم ===
 
=== خطوات إعادة التصميم ===
# إذا كانت الشرطية في تابع ينفذ إجراءات أخرى، قم بإجراء [[Refactoring/extract method|استخراج التوابع]].
+
# إذا كانت البنية الشرطية في تابع ينفذ إجراءات أخرى، فقم بإجراء [[Refactoring/extract method|استخراج التوابع]].
# لكل صنف تسلسل هرمي فرعي، أعِد تعريف التابع الذي يحتوي على الشرطية وانسخ الشيفرة البرمجية للفرع الشرطي المقابل لذلك الموقع.
+
# لكل صنف تسلسل هرمي فرعي، أعِد تعريف التابع الذي يحتوي على البنية الشرطية وانسخ الشيفرة البرمجية للفرع الشرطي المقابل لذلك الموقع.
# احذف هذا الفرع من الشرطية.
+
# احذف هذا الفرع من البنية الشرطية.
# كرر الاستبدال حتى تصبح الشرطيات فارغة. ثم احذف الشرطية وعرِّف نبذة مختصرة عن التابع.
+
# كرر الاستبدال حتى تصبح فروع البنية الشرطية فارغةً. ثم احذف هذه البنية وعرِّف نبذة مختصرة عن التابع.
  
 
== انظر أيضًا ==
 
== انظر أيضًا ==
سطر 214: سطر 216:
 
== مصادر ==
 
== مصادر ==
 
* [https://refactoring.guru/replace-conditional-with-polymorphism صفحة توثيق تبديل الشرطيات بالتعدديّة الشكليّة في موقع refactoring.guru].
 
* [https://refactoring.guru/replace-conditional-with-polymorphism صفحة توثيق تبديل الشرطيات بالتعدديّة الشكليّة في موقع refactoring.guru].
 +
[[تصنيف:Refactoring]]
 +
[[تصنيف:Refactoring Techniques]]
 +
[[تصنيف:Refactoring Simplifying Conditional Expressions]]

المراجعة الحالية بتاريخ 08:59، 19 ديسمبر 2018

المشكلة

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

الحل

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

مثال

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

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

انظر أيضًا

مصادر