Polymorphism في OOP

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

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

مقدمة

في المقالات السابقة من سلسلة OOP، ناقشنا classes، والكائنات، والخصائص، والأساليب، وconstructors، وdestructors، والوراثة، وencapsulation. تساعد هذه المفاهيم المطورين على تنظيم الكود في مكونات ذات معنى وحماية الحالة الداخلية للكائنات.

يعتمد polymorphism على هذه الأفكار من خلال السماح للكائنات بمشاركة واجهة مشتركة بينما تتصرف بشكل مختلف داخليًا. وهذا يجعل من الممكن تصميم أنظمة برمجية أسهل في التوسع والتغيير.

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

ما هو polymorphism في البرمجة كائنية التوجه؟

polymorphism في البرمجة كائنية التوجه يعني أن نفس اسم الطريقة أو interface أو الرسالة يمكن أن تنتج سلوكًا مختلفًا اعتمادًا على الكائن الذي يستقبلها.

كلمة polymorphism تأتي من فكرة "أشكال عديدة". في البرمجة، هذا يعني أنه يمكن تنفيذ إجراء مشترك واحد بطرق مختلفة بواسطة فئات مختلفة.

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

يتيح ذلك للمطورين كتابة الكود التي تعتمد على السلوك الشائع بدلاً من تفاصيل فئة محددة.

لماذا يعد polymorphism مهمًا

يعد polymorphism مهمًا لأنه يقلل من تكرار الكود ويجعل البرامج أكثر مرونة. عندما تشترك فئات متعددة في نفس اسم السلوك أو interface، يمكن للتطبيق العمل معهم بطريقة عامة.

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

مع polymorphism، كل فئة مسؤولة عن سلوكها الخاص. يمكن لكود التطبيق الرئيسي استدعاء نفس الطريقة دون الحاجة إلى معرفة التنفيذ الداخلي الدقيق لكل كائن.

وهذا يجعل النظام أكثر نظافة وقابلية للتطوير وأسهل للتوسع في المستقبل.

polymorphism والتفكير في العالم الحقيقي

من السهل فهم polymorphism من خلال أمثلة من العالم الحقيقي. تخيل أنواعًا مختلفة من المركبات مثل السيارات والدراجات النارية والحافلات والشاحنات. يمكن لجميعهم التحرك، لكن كل منهم يتحرك بطريقة مختلفة وقد يكون له قواعد وسرعة وقدرة وسلوك مختلف.

في OOP، يمكننا تمثيل هذه الفكرة باستخدام طريقة شائعة مثل النقل. يمكن لكل فئة مركبة تنفيذ طريقة النقل بشكل مختلف مع الاستمرار في مشاركة نفس الإجراء العام.

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

أنواع polymorphism

يمكن تطبيق polymorphism بطرق مختلفة حسب لغة البرمجة وتصميم التطبيق. الأنواع الأكثر شيوعًا هي:

  • polymorphism في وقت الترجمة: يتم تحديد السلوك قبل تشغيل البرنامج، غالبًا من خلال التحميل الزائد للأسلوب.

  • polymorphism في وقت التشغيل: يتم تحديد السلوك أثناء تشغيل البرنامج، غالبًا من خلال تجاوز الطريقة والوراثة.

  • polymorphism القائم على interface: تطبق classes المختلفة نفس interface وتوفر سلوكها الخاص.

تدعم بعض لغات البرمجة جميع هذه النماذج، بينما تدعم لغات أخرى أنواعًا محددة فقط. على سبيل المثال، يدعم PHP تجاوز الطريقة وpolymorphism القائم على interface، ولكنه لا يدعم التحميل الزائد للطريقة التقليدية بنفس الطريقة مثل بعض اللغات مثل Java أو C#.

تجاوز الأسلوب

يحدث تجاوز الطريقة عندما يوفر child class نسخته الخاصة من الطريقة الموجودة بالفعل في parent class. هذا هو أحد الأشكال الأكثر شيوعًا لpolymorphism.

على سبيل المثال، قد يكون لدى parent class المسمى Animal طريقة تسمى makeSound. يمكن للفئات الفرعية مثل Dog وCat وBird تجاوز هذه الطريقة وتوفير أصوات مختلفة.

class Animal
{
    public function makeSound(): string
    {
        return 'Some sound';
    }
}

class Dog extends Animal
{
    public function makeSound(): string
    {
        return 'Bark';
    }
}

class Cat extends Animal
{
    public function makeSound(): string
    {
        return 'Meow';
    }
}

في هذا المثال، يرث كل من Dog وCat من Animal، لكن كل فئة توفر تطبيقًا مختلفًا لـ makeSound. اسم الطريقة هو نفسه، لكن النتيجة تعتمد على الكائن.

مثال على polymorphism في وقت التشغيل

يسمح polymorphism في وقت التشغيل للبرنامج بتحديد الطريقة التي يجب تنفيذها أثناء تشغيل التطبيق.

على سبيل المثال:

$animals = [
    new Dog(),
    new Cat(),
];

foreach ($animals as $animal) {
    echo $animal->makeSound();
}

تستدعي الحلقة makeSound على كل كائن. لا يحتاج الرمز إلى التحقق مما إذا كان الكائن كلبًا أم قطة. يعرف كل كائن كيفية الاستجابة لاستدعاء الطريقة.

وهذه واحدة من أقوى فوائد polymorphism. فهو يسمح للكود العام بالعمل مع كائنات محددة دون الارتباط بإحكام بكل فئة.

polymorphism القائم على interface

تُستخدم الinterfaces بشكل شائع لتطبيق polymorphism في تصميم البرامج النظيفة. تحدد interface عقدًا يمكن أن تتبعه فئات متعددة. يجب على كل فئة تنفيذ الأساليب التي تحددها interface، ولكن يمكن لكل فئة توفير منطقها الداخلي الخاص.

على سبيل المثال، يمكن لنظام الدفع تحديد واجهة PaymentMethod:

interface PaymentMethod
{
    public function pay(float $amount): bool;
}

class CreditCardPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process credit card payment
        return true;
    }
}

class PayPalPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process PayPal payment
        return true;
    }
}

يقوم كل من CreditCardPayment وPayPalPayment بتنفيذ نفس interface. يمكن للتطبيق التعامل مع كلا الفئتين كطرق دفع، على الرغم من أن كل فئة تعالج الدفع بشكل مختلف.

استخدام polymorphism في نظام الدفع

يمكن لخدمة الدفع استخدام polymorphism من خلال الاعتماد على واجهة PaymentMethod بدلاً من الاعتماد على فئة دفع محددة.

class CheckoutService
{
    public function checkout(PaymentMethod $paymentMethod, float $amount): bool
    {
        return $paymentMethod->pay($amount);
    }
}

في هذا المثال، لا تهتم CheckoutService بما إذا كان الدفع قد تم عن طريق بطاقة الائتمان أو PayPal أو التحويل البنكي أو أي طريقة دفع مستقبلية أخرى. إنه يتوقع فقط كائنًا يتبع واجهة PaymentMethod.

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

polymorphism والوراثة

غالبًا ما يرتبط polymorphism بالوراثة. عندما ترث classes الفرعية من parent class، يمكنها تجاوز الأساليب الأصلية وتوفير سلوكها الخاص.

يسمح هذا لـ parent class بتعريف بنية عامة بينما تحدد classes الفرعية سلوكًا محددًا. على سبيل المثال، يمكن لفئة الشكل تحديد طريقة حساب المساحة، بينما يمكن لفئات الدائرة والمستطيل والمثلث حساب المساحة بشكل مختلف.

abstract class Shape
{
    abstract public function calculateArea(): float;
}

class Circle extends Shape
{
    public function __construct(private float $radius)
    {
    }

    public function calculateArea(): float
    {
        return pi() * $this->radius * $this->radius;
    }
}

class Rectangle extends Shape
{
    public function __construct(private float $width, private float $height)
    {
    }

    public function calculateArea(): float
    {
        return $this->width * $this->height;
    }
}

كل من الدائرة والمستطيل عبارة عن أشكال، لكنهما يحسبان المساحة بطرق مختلفة. وهذا هو polymorphism من خلال الوراثة وabstraction.

polymorphism والطبقات المجردة

الطبقات المجردة مفيدة أيضًا لpolymorphism. يمكن للclass المجرد تحديد السلوك الشائع ويتطلب من الفصول الفرعية تنفيذ طرق محددة.

على سبيل المثال، قد تحدد فئة الإشعارات المجردة طريقة إرسال. يمكن للفئات الفرعية مثل EmailNotification وSMSNotification وPushNotification تنفيذ طريقة الإرسال بشكل مختلف.

يسمح هذا التصميم للتطبيق بالعمل مع الإشعارات بطريقة عامة مع الاستمرار في دعم قنوات التسليم المختلفة.

polymorphism و Dependency Injection

يصبح polymorphism أكثر قوة عندما يقترن بDependency Injection. يسمح Dependency Injection للclass بتلقي تبعياته من الخارج بدلاً من إنشائها داخليًا.

عندما تعتمد التبعيات على interfaces، يمكن للمطورين استبدال تطبيق بآخر دون تغيير class الرئيسية.

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

polymorphism في مشاريع البرمجيات الحقيقية

يتم استخدام polymorphism في العديد من مشاريع البرمجيات الحقيقية. ويظهر في تطبيقات الويب وinterfaces برمجة التطبيقات والأطر وأنظمة الدفع وأنظمة الإشعارات وأنظمة تخزين الملفات وأنظمة المصادقة وأدوات إعداد التقارير.

في تطبيق Laravel أو Symfony، يمكن استخدام polymorphism عندما تقوم خدمات مختلفة بتنفيذ نفس العقد. على سبيل المثال، يمكن لموفري البريد المختلفين تنفيذ نفس واجهة مرسل البريد. يمكن لموفري التخزين المختلفين تنفيذ نفس واجهة تخزين الملفات. يمكن لأدوار المستخدم المختلفة تنفيذ سلوكيات أذونات مختلفة.

وهذا يجعل الكود أكثر قابلية للتكيف. يمكن للتطبيق تغيير مقدمي الخدمات أو إضافة ميزات جديدة دون إعادة كتابة أجزاء كبيرة من النظام.

polymorphism والكود النظيف

يدعم polymorphism الكود النظيفة لأنه يقلل من المنطق الشرطي ويحسن الclass بين المسؤوليات. يتعامل كل class مع سلوكه الخاص بدلاً من إجبار فئة مركزية واحدة على معرفة كل شيء.

على سبيل المثال، بدون polymorphism، قد يحتوي نظام الخروج على العديد من الشروط مثل هذا:

if ($type === 'credit_card') {
    // pay by credit card
} elseif ($type === 'paypal') {
    // pay by PayPal
} elseif ($type === 'bank_transfer') {
    // pay by bank transfer
}

يصبح الحفاظ على هذا النهج أكثر صعوبة مع إضافة المزيد من طرق الدفع.

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

polymorphism والمبدأ المفتوح المغلق

يرتبط polymorphism ارتباطًا وثيقًا بالمبدأ المفتوح المغلق، وهو أحد مبادئ SOLID. ينص المبدأ المفتوح المغلق على أن البرنامج يجب أن يكون مفتوحًا للتوسيع ولكنه مغلق للتعديل.

وهذا يعني أن المطورين يجب أن يكونوا قادرين على إضافة سلوك جديد دون تغيير الكود المختبرة الحالية. يدعم polymorphism ذلك من خلال السماح للفئات الجديدة بتنفيذ نفس interface أو توسيع نفس class المجردة.

على سبيل المثال، إذا كان أحد التطبيقات يدعم بالفعل مدفوعات بطاقة الائتمان وPayPal، فإن إضافة فئة دفع Apple Pay جديدة يجب ألا تتطلب إعادة كتابة منطق الخروج. يمكن أن تتبع class الجديدة نفس واجهة PaymentMethod.

فوائد polymorphism

يوفر polymorphism العديد من الفوائد في البرمجة كائنية التوجه. فهو يساعد المطورين على تصميم أنظمة يسهل توسيعها واختبارها وصيانتها.

تشمل الفوائد الرئيسية ما يلي:

  • تقليل تكرار الكود.

  • تقليل عبارات if والتبديل المعقدة.

  • تحسين المرونة والتوسع.

  • تسهيل اختبار الكود باستخدام تطبيقات بديلة.

  • دعم العمارة النظيفة وأنماط التصميم.

  • السماح لكائنات مختلفة بمشاركة واجهة مشتركة.

  • تحسين قابلية الصيانة على المدى الطويل في المشاريع الكبيرة.

هذه الفوائد تجعل polymorphism أحد المفاهيم الأكثر قيمة لتطوير البرمجيات الاحترافية.

الأخطاء الشائعة عند استخدام polymorphism

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

خطأ آخر هو إنشاء واجهة ضعيفة لا تمثل عقدًا واضحًا. يجب أن تصف interface سلوكًا ذا معنى. لا ينبغي إنشاؤه فقط لجعل الكود تبدو أكثر تقدمًا.

الخطأ الثالث هو الاعتماد على تفاصيل child class بدلاً من النوع الأصلي أو interface. وهذا يقلل من فائدة polymorphism ويجعل الكود مرتبطًا بإحكام مرة أخرى.

polymorphism الجيد يجب أن يجعل الكود أبسط، وليس أكثر إرباكًا.

أفضل الممارسات لاستخدام polymorphism

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

تتضمن أفضل الممارسات المفيدة ما يلي:

  • استخدم interfaces عندما تشترك فئات مختلفة في نفس عقد السلوك.

  • استخدم abstract classes عندما تشترك classes في المنطق والبنية المشتركة.

  • تجنب الكتل الشرطية الكبيرة عندما يتمكن polymorphism من التعبير عن السلوك بشكل أكثر وضوحًا.

  • احتفظ بأسماء الطرق ذات معنى ومتسقة.

  • تصميم interfaces يدور حول سلوك العمل، وليس التفاصيل الفنية فقط.

  • لا تبالغ في استخدام polymorphism لحل مشاكل بسيطة جدًا.

  • تفضل الاعتماد على abstractionات بدلاً من concrete classes.

تساعد هذه الممارسات المطورين على تطبيق polymorphism بطريقة نظيفة وعملية.

لماذا يهم polymorphism للمبتدئين

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

الفكرة الأساسية بسيطة: يمكن للكائنات المختلفة أن تشترك في نفس اسم الإجراء ولكنها تؤدي هذا الإجراء بشكل مختلف.

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

خاتمة

polymorphism هو مبدأ أساسي في Objec